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

Legend:
Removed from v.60  
changed lines
  Added in v.1285

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26