/[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 261 by bh, Thu Aug 15 17:44:33 2002 UTC revision 1344 by jonathan, Tue Jul 1 16:11:26 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 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, \
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_LEAVE_WINDOW       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
25         wxBITMAP_TYPE_XPM, wxCursor, wxImageFromBitmap, wxPlatform, \
26         wxBeginBusyCursor, wxEndBusyCursor
27    
28    
29    # Export related stuff
30    if wxPlatform == '__WXMSW__':
31        from wxPython.wx import wxMetaFileDC
32    from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK
33    
34  from wxPython import wx  from wxPython import wx
35    
36  from wxproj import point_in_polygon_shape, shape_centroid  from wxproj import point_in_polygon_shape, shape_centroid
37    
   
38  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
39       LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED       LAYER_PROJECTION_CHANGED, \
40         MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
41  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
42       SHAPETYPE_POINT       SHAPETYPE_POINT
43  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
44       ALIGN_LEFT, ALIGN_RIGHT       ALIGN_LEFT, ALIGN_RIGHT
45  from Thuban.Lib.connector import Publisher  from Thuban.Lib.connector import Publisher
46    from Thuban.Model.color import Color, Transparent
47    
48  from renderer import ScreenRenderer, PrinterRender  import resource
49    
50    from selection import Selection
51    from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
52    
53  import labeldialog  import labeldialog
54    
55  from messages import SELECTED_SHAPE, VIEW_POSITION  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
56                         SCALE_CHANGED
57    
58    #from common import ThubanBeginBusyCursor, ThubanEndBusyCursor
59    
60  #  #
61  #   The tools  #   The tools
# Line 129  class ZoomInTool(RectTool): Line 148  class ZoomInTool(RectTool):
148              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
149              sx, sy = self.start              sx, sy = self.start
150              cx, cy = self.current              cx, cy = self.current
151              if sx == cx and sy == cy:              if sx == cx or sy == cy:
152                  # Just a mouse click. Simply zoom in by a factor of two                  # Just a mouse click or a degenerate rectangle. Simply
153                    # zoom in by a factor of two
154                    # FIXME: For a click this is the desired behavior but should we
155                    # really do this for degenrate rectagles as well or
156                    # should we ignore them?
157                  self.view.ZoomFactor(2, center = (cx, cy))                  self.view.ZoomFactor(2, center = (cx, cy))
158              else:              else:
159                  # A drag. Zoom in to the rectangle                  # A drag. Zoom in to the rectangle
# Line 149  class ZoomOutTool(RectTool): Line 172  class ZoomOutTool(RectTool):
172              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
173              sx, sy = self.start              sx, sy = self.start
174              cx, cy = self.current              cx, cy = self.current
175              if sx == cx and sy == cy:              if sx == cx or sy == cy:
176                  # Just a mouse click. Simply zoom out by a factor of two                  # Just a mouse click or a degenerate rectangle. Simply
177                    # zoom out by a factor of two.
178                    # FIXME: For a click this is the desired behavior but should we
179                    # really do this for degenrate rectagles as well or
180                    # should we ignore them?
181                  self.view.ZoomFactor(0.5, center = (cx, cy))                  self.view.ZoomFactor(0.5, center = (cx, cy))
182              else:              else:
183                  # A drag. Zoom out to the rectangle                  # A drag. Zoom out to the rectangle
# Line 207  class LabelTool(Tool): Line 234  class LabelTool(Tool):
234          self.view.LabelShapeAt(event.m_x, event.m_y)          self.view.LabelShapeAt(event.m_x, event.m_y)
235    
236    
   
   
237  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
238    
239      """      """
240      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
241      """      """
242    
243      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
244          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
245            self.canvas = canvas
246          self.map = map          self.map = map
247            self.region = region
248            self.selected_layer = selected_layer
249            self.selected_shapes = selected_shapes
250    
251      def GetPageInfo(self):      def GetPageInfo(self):
252          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 231  class MapPrintout(wx.wxPrintout): Line 260  class MapPrintout(wx.wxPrintout):
260    
261      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
262          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
263          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = OutputTransform(self.canvas.scale,
264          scalex = width / (urx - llx)                                                     self.canvas.offset,
265          scaley = height / (ury - lly)                                                     self.canvas.GetSizeTuple(),
266          scale = min(scalex, scaley)                                                     self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
267          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
268          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          renderer = PrinterRenderer(dc, scale, offset, resolution = resy)
269          renderer.RenderMap(self.map)          x, y, width, height = self.region
270          return wx.true          canvas_scale = self.canvas.scale
271            renderer.RenderMap(self.map,
272                               (0,0,
273                                    (width/canvas_scale)*scale,
274                                    (height/canvas_scale)*scale),
275                                    mapregion,
276                               self.selected_layer, self.selected_shapes)
277            return True
278    
279  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, Publisher):
280    
281      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
282    
283      def __init__(self, parent, winid, interactor):      # Some messages that can be subscribed/unsubscribed directly through
284        # the MapCanvas come in fact from other objects. This is a dict
285        # mapping those messages to the names of the instance variables they
286        # actually come from. The delegation is implemented in the Subscribe
287        # and Unsubscribe methods
288        delegated_messages = {LAYER_SELECTED: "selection",
289                              SHAPES_SELECTED: "selection"}
290    
291        # Methods delegated to some instance variables. The delegation is
292        # implemented in the __getattr__ method.
293        delegated_methods = {"SelectLayer": "selection",
294                             "SelectShapes": "selection",
295                             "SelectedLayer": "selection",
296                             "HasSelectedLayer": "selection",
297                             "HasSelectedShapes": "selection",
298                             "SelectedShapes": "selection"}
299    
300        def __init__(self, parent, winid):
301          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
302          self.SetBackgroundColour(wxColour(255, 255, 255))          self.SetBackgroundColour(wxColour(255, 255, 255))
303    
304          # the map displayed in this canvas. Set with SetMap()          # the map displayed in this canvas. Set with SetMap()
305          self.map = None          self.map = None
306    
307            # current map projection. should only differ from map.projection
308            # when the map's projection is changing and we need access to the
309            # old projection.
310            self.current_map_proj = None
311    
312          # scale and offset describe the transformation from projected          # scale and offset describe the transformation from projected
313          # coordinates to window coordinates.          # coordinates to window coordinates.
314          self.scale = 1.0          self.scale = 1.0
# Line 271  class MapCanvas(wxWindow, Publisher): Line 325  class MapCanvas(wxWindow, Publisher):
325          # if the mouse is outside the window.          # if the mouse is outside the window.
326          self.current_position = None          self.current_position = None
327    
         # If true, OnIdle will call do_redraw to do the actual  
         # redrawing. Set by OnPaint to avoid some unnecessary redraws.  
         # To force a redraw call full_redraw().  
         self.redraw_on_idle = 0  
   
         # The region to update when idle  
         self.update_region = wx.wxRegion()  
   
328          # the bitmap serving as backing store          # the bitmap serving as backing store
329          self.bitmap = None          self.bitmap = None
330    
331          # the interactor          # the selection
332          self.interactor = interactor          self.selection = Selection()
333          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)          self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
334    
335          # keep track of which layers/shapes are selected to make sure we          # keep track of which layers/shapes are selected to make sure we
336          # only redraw when necessary          # only redraw when necessary
337          self.last_selected_layer = None          self.last_selected_layer = None
338          self.last_selected_shape = None          self.last_selected_shape = None
339    
340            self.backgroundColor = wx.wxWHITE_BRUSH
341    
342          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
343          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
344          EVT_LEFT_DOWN(self, self.OnLeftDown)          EVT_LEFT_DOWN(self, self.OnLeftDown)
# Line 304  class MapCanvas(wxWindow, Publisher): Line 352  class MapCanvas(wxWindow, Publisher):
352          wxWindow.__del__(self)          wxWindow.__del__(self)
353          Publisher.__del__(self)          Publisher.__del__(self)
354    
355        def Subscribe(self, channel, *args):
356            """Extend the inherited method to handle delegated messages.
357    
358            If channel is one of the delegated messages call the appropriate
359            object's Subscribe method. Otherwise just call the inherited
360            method.
361            """
362            if channel in self.delegated_messages:
363                object = getattr(self, self.delegated_messages[channel])
364                object.Subscribe(channel, *args)
365            else:
366                Publisher.Subscribe(self, channel, *args)
367    
368        def Unsubscribe(self, channel, *args):
369            """Extend the inherited method to handle delegated messages.
370    
371            If channel is one of the delegated messages call the appropriate
372            object's Unsubscribe method. Otherwise just call the inherited
373            method.
374            """
375            if channel in self.delegated_messages:
376                object = getattr(self, self.delegated_messages[channel])
377                object.Unsubscribe(channel, *args)
378            else:
379                Publisher.Unsubscribe(self, channel, *args)
380    
381        def __getattr__(self, attr):
382            if attr in self.delegated_methods:
383                return getattr(getattr(self, self.delegated_methods[attr]), attr)
384            raise AttributeError(attr)
385    
386      def OnPaint(self, event):      def OnPaint(self, event):
387          dc = wxPaintDC(self)          dc = wxPaintDC(self)
388    
389          if self.map is not None and self.map.HasLayers():          if self.map is not None and self.map.HasLayers():
390              # We have a non-empty map. Redraw it in idle time              if self.bitmap in (None, -1):
391              self.redraw_on_idle = 1                  # set the flag that we should redraw the
392              # update the region that has to be redrawn                  # bitmap in idle time
393              self.update_region.UnionRegion(self.GetUpdateRegion())                  self.bitmap = -1
394                    return
395    
396                # blit the bitmap to the screen
397                dc.BeginDrawing()
398                dc.DrawBitmap(self.bitmap, 0, 0)
399                dc.EndDrawing()
400          else:          else:
401              # If we've got no map or if the map is empty, simply clear              # If we've got no map or if the map is empty, simply clear
402              # the screen.              # the screen.
# Line 320  class MapCanvas(wxWindow, Publisher): Line 406  class MapCanvas(wxWindow, Publisher):
406              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
407              # with a true argument in the right places.              # with a true argument in the right places.
408              dc.BeginDrawing()              dc.BeginDrawing()
409                dc.SetBackground(self.backgroundColor)
410              dc.Clear()              dc.Clear()
411              dc.EndDrawing()              dc.EndDrawing()
412    
413              # clear the region      def OnIdle(self, event):
414              self.update_region = wx.wxRegion()          # render the screen if necessary
   
     def do_redraw(self):  
         # This should only be called if we have a non-empty map.  
415    
416          # get the update region and reset it. We're not actually using          if self.bitmap != -1:
417          # it anymore, though.              return
         update_box = self.update_region.GetBox()  
         self.update_region = wx.wxRegion()  
418    
419          # Get the window size.          wxBeginBusyCursor()
420          width, height = self.GetSizeTuple()          try:
421                width, height = self.GetSizeTuple()
422    
         # If self.bitmap's still there, reuse it. Otherwise redraw it  
         if self.bitmap is not None:  
             bitmap = self.bitmap  
         else:  
423              bitmap = wx.wxEmptyBitmap(width, height)              bitmap = wx.wxEmptyBitmap(width, height)
424              dc = wx.wxMemoryDC()              dc = wx.wxMemoryDC()
425              dc.SelectObject(bitmap)              dc.SelectObject(bitmap)
426              dc.BeginDrawing()              dc.BeginDrawing()
427    
428              # clear the background              dc.SetBackground(self.backgroundColor)
429              dc.SetBrush(wx.wxWHITE_BRUSH)              dc.Clear()
430              dc.SetPen(wx.wxTRANSPARENT_PEN)  
431              dc.DrawRectangle(0, 0, width, height)              selected_layer = self.selection.SelectedLayer()
432                selected_shapes = self.selection.SelectedShapes()
             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  
433    
434              # draw the map into the bitmap              # draw the map into the bitmap
435              renderer = ScreenRenderer(dc, self.scale, self.offset)              renderer = ScreenRenderer(dc, self.scale, self.offset)
436    
437              # Pass the entire bitmap as update_region to the renderer.              # Pass the entire bitmap as update region to the renderer.
438              # We're redrawing the whole bitmap, after all.              # We're redrawing the whole bitmap, after all.
439              renderer.RenderMap(self.map, (0, 0, width, height),              renderer.RenderMap(self.map, (0, 0, width, height),
440                                 selected_layer, selected_shape)                                 selected_layer, selected_shapes)
441    
442              dc.EndDrawing()              dc.EndDrawing()
443              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.wxNullBitmap)
444    
445              self.bitmap = bitmap              self.bitmap = bitmap
446            finally:
447                wxEndBusyCursor()
448                pass
449    
450            # This causes a paint event that then draws the bitmap
451            self.redraw()
452    
453        def Export(self):
454            if self.scale == 0:
455                return
456    
457            if hasattr(self, "export_path"):
458                export_path = self.export_path
459            else:
460                export_path="."
461            dlg = wxFileDialog(self, _("Export Map"), export_path, "",
462                               "Enhanced Metafile (*.wmf)|*.wmf",
463                               wxSAVE|wxOVERWRITE_PROMPT)
464            if dlg.ShowModal() == wxID_OK:
465                self.export_path = os.path.dirname(dlg.GetPath())
466                dc = wxMetaFileDC(dlg.GetPath())
467        
468                scale, offset, mapregion = OutputTransform(self.scale,
469                                                           self.offset,
470                                                           self.GetSizeTuple(),
471                                                           dc.GetSizeTuple())
472    
473                selected_layer = self.selection.SelectedLayer()
474                selected_shapes = self.selection.SelectedShapes()
475    
476          # blit the bitmap to the screen              renderer = ExportRenderer(dc, scale, offset)
         dc = wx.wxMemoryDC()  
         dc.SelectObject(bitmap)  
         clientdc = wxClientDC(self)  
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
477    
478                # Pass the entire bitmap as update region to the renderer.
479                # We're redrawing the whole bitmap, after all.
480                width, height = self.GetSizeTuple()
481                renderer.RenderMap(self.map,
482                                    (0,0,
483                                        (width/self.scale)*scale,
484                                        (height/self.scale)*scale),
485                                    mapregion,
486                                    selected_layer, selected_shapes)
487                dc.EndDrawing()
488                dc.Close()
489            dlg.Destroy()
490            
491      def Print(self):      def Print(self):
492          printer = wx.wxPrinter()          printer = wx.wxPrinter()
493          printout = MapPrintout(self.map)          width, height = self.GetSizeTuple()
494          printer.Print(self, printout, wx.true)          selected_layer = self.selection.SelectedLayer()
495            selected_shapes = self.selection.SelectedShapes()
496            
497            printout = MapPrintout(self, self.map, (0, 0, width, height),
498                                   selected_layer, selected_shapes)
499            printer.Print(self, printout, True)
500          printout.Destroy()          printout.Destroy()
501    
502      def SetMap(self, map):      def SetMap(self, map):
503          redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
504                             LAYER_VISIBILITY_CHANGED)                             LAYER_VISIBILITY_CHANGED)
505          if self.map is not None:          if self.map is not None:
506              for channel in redraw_channels:              for channel in redraw_channels:
507                  self.map.Unsubscribe(channel, self.full_redraw)                  self.map.Unsubscribe(channel, self.full_redraw)
508              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
509                                   self.projection_changed)                                   self.map_projection_changed)
510                self.map.Unsubscribe(LAYER_PROJECTION_CHANGED,
511                                     self.layer_projection_changed)
512          self.map = map          self.map = map
513            self.current_map_proj = self.map.GetProjection()
514            self.selection.ClearSelection()
515          if self.map is not None:          if self.map is not None:
516              for channel in redraw_channels:              for channel in redraw_channels:
517                  self.map.Subscribe(channel, self.full_redraw)                  self.map.Subscribe(channel, self.full_redraw)
518              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.map_projection_changed)
519                self.map.Subscribe(LAYER_PROJECTION_CHANGED, self.layer_projection_changed)
520          self.FitMapToWindow()          self.FitMapToWindow()
521          # force a redraw. If map is not empty, it's already been called          # force a redraw. If map is not empty, it's already been called
522          # by FitMapToWindow but if map is empty it hasn't been called          # by FitMapToWindow but if map is empty it hasn't been called
# Line 404  class MapCanvas(wxWindow, Publisher): Line 524  class MapCanvas(wxWindow, Publisher):
524          self.full_redraw()          self.full_redraw()
525    
526      def Map(self):      def Map(self):
527            """Return the map displayed by this canvas"""
528          return self.map          return self.map
529    
530      def redraw(self, *args):      def redraw(self, *args):
531          self.Refresh(0)          self.Refresh(False)
532    
533      def full_redraw(self, *args):      def full_redraw(self, *args):
534          self.bitmap = None          self.bitmap = None
535          self.redraw()          self.redraw()
536    
537      def projection_changed(self, *args):      def map_projection_changed(self, *args):
538          self.FitMapToWindow()  
539            proj = self.current_map_proj
540            self.current_map_proj = self.map.GetProjection()
541    
542            bbox = None
543    
544            if proj is not None and self.current_map_proj is not None:
545                width, height = self.GetSizeTuple()
546                llx, lly = self.win_to_proj(0, height)
547                urx, ury = self.win_to_proj(width, 0)
548                bbox = proj.Inverse(llx, lly) + proj.Inverse(urx, ury)
549                bbox = self.current_map_proj.ForwardBBox(bbox)
550    
551            if bbox is not None:
552                self.FitRectToWindow(bbox)
553            else:
554                self.FitMapToWindow()
555    
556            self.full_redraw()
557    
558        def layer_projection_changed(self, *args):
559          self.full_redraw()          self.full_redraw()
560    
561      def set_view_transform(self, scale, offset):      def set_view_transform(self, scale, offset):
562            # width/height of the projected bbox
563            llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()
564            pwidth = float(urx - llx)
565            pheight = float(ury - lly)
566    
567            # width/height of the window
568            wwidth, wheight = self.GetSizeTuple()
569    
570            # The window's center in projected coordinates assuming the new
571            # scale/offset
572            pcenterx = (wwidth/2 - offset[0]) / scale
573            pcentery = (offset[1] - wheight/2) / scale
574    
575            # The window coordinates used when drawing the shapes must fit
576            # into 16bit signed integers.
577            max_len = max(pwidth, pheight)
578            if max_len:
579                max_scale = 32000.0 / max_len
580            else:
581                # FIXME: What to do in this case? The bbox is effectively
582                # empty so any scale should work.
583                max_scale = scale
584    
585            # The minimal scale is somewhat arbitrarily set to half that of
586            # the bbox fit into the window
587            scales = []
588            if pwidth:
589                scales.append(wwidth / pwidth)
590            if pheight:
591                scales.append(wheight / pheight)
592            if scales:
593                min_scale = 0.5 * min(scales)
594            else:
595                min_scale = scale
596    
597            if scale > max_scale:
598                scale = max_scale
599            elif scale < min_scale:
600                scale = min_scale
601    
602          self.scale = scale          self.scale = scale
603          self.offset = offset  
604            # determine new offset to preserve the center
605            self.offset = (wwidth/2 - scale * pcenterx,
606                           wheight/2 + scale * pcentery)
607          self.full_redraw()          self.full_redraw()
608            self.issue(SCALE_CHANGED, scale)
609    
610      def proj_to_win(self, x, y):      def proj_to_win(self, x, y):
611          """\          """\
612          Return the point in  window coords given by projected coordinates x y          Return the point in  window coords given by projected coordinates x y
613          """          """
614            if self.scale == 0:
615                return (0, 0)
616    
617          offx, offy = self.offset          offx, offy = self.offset
618          return (self.scale * x + offx, -self.scale * y + offy)          return (self.scale * x + offx, -self.scale * y + offy)
619    
# Line 433  class MapCanvas(wxWindow, Publisher): Line 621  class MapCanvas(wxWindow, Publisher):
621          """\          """\
622          Return the point in projected coordinates given by window coords x y          Return the point in projected coordinates given by window coords x y
623          """          """
624            if self.scale == 0:
625                return (0, 0)
626    
627          offx, offy = self.offset          offx, offy = self.offset
628          return ((x - offx) / self.scale, (offy - y) / self.scale)          return ((x - offx) / self.scale, (offy - y) / self.scale)
629    
630      def FitRectToWindow(self, rect):      def FitRectToWindow(self, rect):
631            """Fit the rectangular region given by rect into the window.
632    
633            Set scale so that rect (in projected coordinates) just fits into
634            the window and center it.
635            """
636          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
637          llx, lly, urx, ury = rect          llx, lly, urx, ury = rect
638          if llx == urx or lly == ury:          if llx == urx or lly == ury:
639              # zero with or zero height. Do Nothing              # zero width or zero height. Do Nothing
640              return              return
641          scalex = width / (urx - llx)          scalex = width / (urx - llx)
642          scaley = height / (ury - lly)          scaley = height / (ury - lly)
# Line 450  class MapCanvas(wxWindow, Publisher): Line 646  class MapCanvas(wxWindow, Publisher):
646          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
647    
648      def FitMapToWindow(self):      def FitMapToWindow(self):
649          """\          """Fit the map to the window
650          Set the scale and offset so that the map is centered in the  
651          window          Set the scale so that the map fits exactly into the window and
652            center it in the window.
653          """          """
654          bbox = self.map.ProjectedBoundingBox()          if self.map is not None:
655                bbox = self.map.ProjectedBoundingBox()
656                if bbox is not None:
657                    self.FitRectToWindow(bbox)
658    
659        def FitLayerToWindow(self, layer):
660            """Fit the given layer to the window.
661    
662            Set the scale so that the layer fits exactly into the window and
663            center it in the window.
664            """
665            
666            bbox = layer.LatLongBoundingBox()
667          if bbox is not None:          if bbox is not None:
668              self.FitRectToWindow(bbox)              proj = self.map.GetProjection()
669                if proj is not None:
670                    bbox = proj.ForwardBBox(bbox)
671    
672                if bbox is not None:
673                    self.FitRectToWindow(bbox)
674    
675        def FitSelectedToWindow(self):
676            layer = self.selection.SelectedLayer()
677            shapes = self.selection.SelectedShapes()
678    
679            bbox = layer.ShapesBoundingBox(shapes)
680            if bbox is not None:
681                proj = self.map.GetProjection()
682                if proj is not None:
683                    bbox = proj.ForwardBBox(bbox)
684    
685                if bbox is not None:
686                    if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:
687                        self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))
688                    else:
689                        self.FitRectToWindow(bbox)
690    
691      def ZoomFactor(self, factor, center = None):      def ZoomFactor(self, factor, center = None):
692          """Multiply the zoom by factor and center on center.          """Multiply the zoom by factor and center on center.
# Line 465  class MapCanvas(wxWindow, Publisher): Line 695  class MapCanvas(wxWindow, Publisher):
695          that should be centered. If it is omitted, it defaults to the          that should be centered. If it is omitted, it defaults to the
696          center of the window          center of the window
697          """          """
698          width, height = self.GetSizeTuple()          if self.scale > 0:
699          scale = self.scale * factor              width, height = self.GetSizeTuple()
700          offx, offy = self.offset              scale = self.scale * factor
701          if center is not None:              offx, offy = self.offset
702              cx, cy = center              if center is not None:
703          else:                  cx, cy = center
704              cx = width / 2              else:
705              cy = height / 2                  cx = width / 2
706          offset = (factor * (offx - cx) + width / 2,                  cy = height / 2
707                    factor * (offy - cy) + height / 2)              offset = (factor * (offx - cx) + width / 2,
708          self.set_view_transform(scale, offset)                      factor * (offy - cy) + height / 2)
709                self.set_view_transform(scale, offset)
710    
711      def ZoomOutToRect(self, rect):      def ZoomOutToRect(self, rect):
712          # rect is given in window coordinates          """Zoom out to fit the currently visible region into rect.
713    
714            The rect parameter is given in window coordinates
715            """
716          # determine the bbox of the displayed region in projected          # determine the bbox of the displayed region in projected
717          # coordinates          # coordinates
718          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
# Line 496  class MapCanvas(wxWindow, Publisher): Line 729  class MapCanvas(wxWindow, Publisher):
729          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
730    
731      def Translate(self, dx, dy):      def Translate(self, dx, dy):
732            """Move the map by dx, dy pixels"""
733          offx, offy = self.offset          offx, offy = self.offset
734          self.set_view_transform(self.scale, (offx + dx, offy + dy))          self.set_view_transform(self.scale, (offx + dx, offy + dy))
735    
736        def SelectTool(self, tool):
737            """Make tool the active tool.
738    
739            The parameter should be an instance of Tool or None to indicate
740            that no tool is active.
741            """
742            self.tool = tool
743    
744      def ZoomInTool(self):      def ZoomInTool(self):
745          self.tool = ZoomInTool(self)          """Start the zoom in tool"""
746            self.SelectTool(ZoomInTool(self))
747    
748      def ZoomOutTool(self):      def ZoomOutTool(self):
749          self.tool = ZoomOutTool(self)          """Start the zoom out tool"""
750            self.SelectTool(ZoomOutTool(self))
751    
752      def PanTool(self):      def PanTool(self):
753          self.tool = PanTool(self)          """Start the pan tool"""
754            self.SelectTool(PanTool(self))
755            #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
756            #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
757            #print bmp
758            #img = wxImageFromBitmap(bmp)
759            #print img
760            #cur = wxCursor(img)
761            #print cur
762            #self.SetCursor(cur)
763    
764      def IdentifyTool(self):      def IdentifyTool(self):
765          self.tool = IdentifyTool(self)          """Start the identify tool"""
766            self.SelectTool(IdentifyTool(self))
767    
768      def LabelTool(self):      def LabelTool(self):
769          self.tool = LabelTool(self)          """Start the label tool"""
770            self.SelectTool(LabelTool(self))
771    
772      def CurrentTool(self):      def CurrentTool(self):
773            """Return the name of the current tool or None if no tool is active"""
774          return self.tool and self.tool.Name() or None          return self.tool and self.tool.Name() or None
775    
776      def CurrentPosition(self):      def CurrentPosition(self):
# Line 557  class MapCanvas(wxWindow, Publisher): Line 813  class MapCanvas(wxWindow, Publisher):
813          self.set_current_position(event)          self.set_current_position(event)
814          if self.dragging:          if self.dragging:
815              self.ReleaseMouse()              self.ReleaseMouse()
816              self.tool.Hide(self.drag_dc)              try:
817              self.tool.MouseUp(event)                  self.tool.Hide(self.drag_dc)
818              self.drag_dc = None                  self.tool.MouseUp(event)
819          self.dragging = 0              finally:
820                    self.drag_dc = None
821                    self.dragging = 0
822    
823      def OnMotion(self, event):      def OnMotion(self, event):
824          self.set_current_position(event)          self.set_current_position(event)
# Line 572  class MapCanvas(wxWindow, Publisher): Line 830  class MapCanvas(wxWindow, Publisher):
830      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
831          self.set_current_position(None)          self.set_current_position(None)
832    
     def OnIdle(self, event):  
         if self.redraw_on_idle:  
             self.do_redraw()  
         self.redraw_on_idle = 0  
   
833      def OnSize(self, event):      def OnSize(self, event):
834          # the window's size has changed. We have to get a new bitmap. If          # the window's size has changed. We have to get a new bitmap. If
835          # we want to be clever we could try to get by without throwing          # we want to be clever we could try to get by without throwing
# Line 585  class MapCanvas(wxWindow, Publisher): Line 838  class MapCanvas(wxWindow, Publisher):
838          # Even when the window becomes larger some parts of the bitmap          # Even when the window becomes larger some parts of the bitmap
839          # could be reused.          # could be reused.
840          self.full_redraw()          self.full_redraw()
841            pass
842    
843      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
844          """Redraw the map.          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
845            # The selection object takes care that it only issues
846          Receiver for the SELECTED_SHAPE messages. Try to redraw only          # SHAPES_SELECTED messages when the set of selected shapes has
847          when necessary.          # actually changed, so we can do a full redraw unconditionally.
848          """          # FIXME: We should perhaps try to limit the redraw to the are
849          # A redraw is necessary when the display has to change, which          # actually covered by the shapes before and after the selection
850          # means that either the status changes from having no selection          # change.
851          # to having a selection shape or vice versa, or when the fact          self.full_redraw()
         # whether there is a selection at all doesn't change, when the  
         # shape which is selected has changed (which means that layer or  
         # shapeid changes).  
         if ((shape is not None or self.last_selected_shape is not None)  
             and (shape != self.last_selected_shape  
                  or layer != self.last_selected_layer)):  
             self.full_redraw()  
   
         # remember the selection so we can compare when it changes again.  
         self.last_selected_layer = layer  
         self.last_selected_shape = shape  
852    
853      def unprojected_rect_around_point(self, x, y):      def unprojected_rect_around_point(self, x, y, dist):
854          """return a rect a few pixels around (x, y) in unprojected corrdinates          """return a rect dist pixels around (x, y) in unprojected corrdinates
855    
856          The return value is a tuple (minx, miny, maxx, maxy) suitable a          The return value is a tuple (minx, miny, maxx, maxy) suitable a
857          parameter to a layer's ShapesInRegion method.          parameter to a layer's ShapesInRegion method.
# Line 622  class MapCanvas(wxWindow, Publisher): Line 865  class MapCanvas(wxWindow, Publisher):
865          xs = []          xs = []
866          ys = []          ys = []
867          for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):          for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
868              px, py = self.win_to_proj(x + dx, y + dy)              px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
869              if inverse:              if inverse:
870                  px, py = inverse(px, py)                  px, py = inverse(px, py)
871              xs.append(px)              xs.append(px)
# Line 649  class MapCanvas(wxWindow, Publisher): Line 892  class MapCanvas(wxWindow, Publisher):
892              forward = None              forward = None
893    
894          scale = self.scale          scale = self.scale
         offx, offy = self.offset  
895    
896          box = self.unprojected_rect_around_point(px, py)          if scale == 0:
897                return None, None
898    
899            offx, offy = self.offset
900    
901          if select_labels:          if select_labels:
902              labels = self.map.LabelLayer().Labels()              labels = self.map.LabelLayer().Labels()
# Line 696  class MapCanvas(wxWindow, Publisher): Line 941  class MapCanvas(wxWindow, Publisher):
941              layer = layers[layer_index]              layer = layers[layer_index]
942    
943              # search only in visible layers              # search only in visible layers
944              if not layer.Visible():              if not layer.Visible() or not layer.HasShapes():
945                  continue                  continue
946    
947              filled = layer.fill is not None              filled = layer.GetClassification().GetDefaultFill() \
948              stroked = layer.stroke is not None                       is not Transparent
949                stroked = layer.GetClassification().GetDefaultLineColor() \
950                          is not Transparent
951    
952              layer_proj = layer.projection              layer_proj = layer.projection
953              if layer_proj is not None:              if layer_proj is not None:
# Line 712  class MapCanvas(wxWindow, Publisher): Line 959  class MapCanvas(wxWindow, Publisher):
959    
960              select_shape = -1              select_shape = -1
961    
962                # Determine the ids of the shapes that overlap a tiny area
963                # around the point. For layers containing points we have to
964                # choose a larger size of the box we're testing agains so
965                # that we take the size of the markers into account
966                # FIXME: Once the markers are more flexible this part has to
967                # become more flexible too, of course
968                if shapetype == SHAPETYPE_POINT:
969                    box = self.unprojected_rect_around_point(px, py, 5)
970                else:
971                    box = self.unprojected_rect_around_point(px, py, 1)
972              shape_ids = layer.ShapesInRegion(box)              shape_ids = layer.ShapesInRegion(box)
973              shape_ids.reverse()              shape_ids.reverse()
974    
975              if shapetype == SHAPETYPE_POLYGON:              if shapetype == SHAPETYPE_POLYGON:
976                  for i in shape_ids:                  for i in shape_ids:
977                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
978                                                      i,                      result = point_in_polygon_shape(shapefile, i,
979                                                      filled, stroked,                                                      filled, stroked,
980                                                      map_proj, layer_proj,                                                      map_proj, layer_proj,
981                                                      scale, -scale, offx, offy,                                                      scale, -scale, offx, offy,
# Line 728  class MapCanvas(wxWindow, Publisher): Line 985  class MapCanvas(wxWindow, Publisher):
985                          break                          break
986              elif shapetype == SHAPETYPE_ARC:              elif shapetype == SHAPETYPE_ARC:
987                  for i in shape_ids:                  for i in shape_ids:
988                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
989                        result = point_in_polygon_shape(shapefile,
990                                                      i, 0, 1,                                                      i, 0, 1,
991                                                      map_proj, layer_proj,                                                      map_proj, layer_proj,
992                                                      scale, -scale, offx, offy,                                                      scale, -scale, offx, offy,
# Line 769  class MapCanvas(wxWindow, Publisher): Line 1027  class MapCanvas(wxWindow, Publisher):
1027          # to deselect the currently selected layer, so we simply select          # to deselect the currently selected layer, so we simply select
1028          # the already selected layer again.          # the already selected layer again.
1029          if layer is None:          if layer is None:
1030              layer = self.interactor.SelectedLayer()              layer = self.selection.SelectedLayer()
1031          self.interactor.SelectLayerAndShape(layer, shape)              shapes = []
1032            else:
1033                shapes = [shape]
1034            self.selection.SelectShapes(layer, shapes)
1035          return result          return result
1036    
1037      def LabelShapeAt(self, x, y):      def LabelShapeAt(self, x, y):
1038            """Add or remove a label at window position x, y.
1039    
1040            If there's a label at the given position, remove it. Otherwise
1041            determine the shape at the position, run the label dialog and
1042            unless the user cancels the dialog, add a laber.
1043            """
1044          ox = x; oy = y          ox = x; oy = y
1045          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
1046          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 781  class MapCanvas(wxWindow, Publisher): Line 1048  class MapCanvas(wxWindow, Publisher):
1048              # a label was selected              # a label was selected
1049              label_layer.RemoveLabel(shape_index)              label_layer.RemoveLabel(shape_index)
1050          elif layer is not None:          elif layer is not None:
1051              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
1052                                                    layer.ShapeStore().Table(),
1053                                                    shape_index)
1054              if text:              if text:
1055                  proj = self.map.projection                  proj = self.map.projection
1056                  if proj is not None:                  if proj is not None:
# Line 796  class MapCanvas(wxWindow, Publisher): Line 1065  class MapCanvas(wxWindow, Publisher):
1065    
1066                  shapetype = layer.ShapeType()                  shapetype = layer.ShapeType()
1067                  if shapetype == SHAPETYPE_POLYGON:                  if shapetype == SHAPETYPE_POLYGON:
1068                      x, y = shape_centroid(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
1069                                            shape_index,                      x, y = shape_centroid(shapefile, shape_index,
1070                                            map_proj, layer_proj, 1, 1, 0, 0)                                            map_proj, layer_proj, 1, 1, 0, 0)
1071                      if map_proj is not None:                      if map_proj is not None:
1072                          x, y = map_proj.Inverse(x, y)                          x, y = map_proj.Inverse(x, y)
# Line 822  class MapCanvas(wxWindow, Publisher): Line 1091  class MapCanvas(wxWindow, Publisher):
1091                      valign = ALIGN_CENTER                      valign = ALIGN_CENTER
1092                  label_layer.AddLabel(x, y, text,                  label_layer.AddLabel(x, y, text,
1093                                       halign = halign, valign = valign)                                       halign = halign, valign = valign)
1094    
1095    def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):
1096        """Calculate dimensions to transform canvas content to output device."""
1097        width, height = device_extend
1098    
1099        # Only 80 % of the with are available for the map
1100        width = width * 0.8
1101    
1102        # Define the distance of the map from DC border
1103        distance = 20
1104    
1105        if height < width:
1106            # landscape
1107            map_height = height - 2*distance
1108            map_width = map_height
1109        else:
1110            # portrait, recalibrate width (usually the legend width is too
1111            # small
1112            width = width * 0.9
1113            map_height = width - 2*distance
1114            map_width = map_height
1115        
1116        mapregion = (distance, distance,
1117                     distance+map_width, distance+map_height)
1118    
1119        canvas_width, canvas_height = canvas_size
1120        
1121        scalex = map_width / (canvas_width/canvas_scale)
1122        scaley = map_height / (canvas_height/canvas_scale)
1123        scale = min(scalex, scaley)
1124        canvas_offx, canvas_offy = canvas_offset
1125        offx = scale*canvas_offx/canvas_scale
1126        offy = scale*canvas_offy/canvas_scale
1127    
1128        return scale, (offx, offy), mapregion

Legend:
Removed from v.261  
changed lines
  Added in v.1344

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26