/[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 6 by bh, Tue Aug 28 15:41:52 2001 UTC revision 1344 by jonathan, Tue Jul 1 16:11:26 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, Transparent
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    #from common import ThubanBeginBusyCursor, ThubanEndBusyCursor
59    
60  #  #
61  #   The tools  #   The tools
# Line 127  class ZoomInTool(RectTool): Line 146  class ZoomInTool(RectTool):
146      def MouseUp(self, event):      def MouseUp(self, event):
147          if self.dragging:          if self.dragging:
148              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
149              self.view.FitRectToWindow(self.proj_rect())              sx, sy = self.start
150                cx, cy = self.current
151                if sx == cx or sy == cy:
152                    # 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))
158                else:
159                    # A drag. Zoom in to the rectangle
160                    self.view.FitRectToWindow(self.proj_rect())
161    
162    
163  class ZoomOutTool(RectTool):  class ZoomOutTool(RectTool):
164    
165      """The Zoom-Out Tool"""      """The Zoom-Out Tool"""
166        
167      def Name(self):      def Name(self):
168          return "ZoomOutTool"          return "ZoomOutTool"
169    
# Line 142  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              self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),              if sx == cx or sy == cy:
176                                       max(sx, cx), max(sy, cy)))                  # 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))
182                else:
183                    # A drag. Zoom out to the rectangle
184                    self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
185                                             max(sx, cx), max(sy, cy)))
186    
187    
188  class PanTool(Tool):  class PanTool(Tool):
# Line 155  class PanTool(Tool): Line 194  class PanTool(Tool):
194    
195      def MouseMove(self, event):      def MouseMove(self, event):
196          if self.dragging:          if self.dragging:
             x0, y0 = self.current  
197              Tool.MouseMove(self, event)              Tool.MouseMove(self, event)
198                sx, sy = self.start
199              x, y = self.current              x, y = self.current
200              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
201    
202                bitmapdc = wx.wxMemoryDC()
203                bitmapdc.SelectObject(self.view.bitmap)
204    
205              dc = self.view.drag_dc              dc = self.view.drag_dc
206              dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
207    
208      def MouseUp(self, event):      def MouseUp(self, event):
209          if self.dragging:          if self.dragging:
# Line 168  class PanTool(Tool): Line 211  class PanTool(Tool):
211              sx, sy = self.start              sx, sy = self.start
212              cx, cy = self.current              cx, cy = self.current
213              self.view.Translate(cx - sx, cy - sy)              self.view.Translate(cx - sx, cy - sy)
214            
215  class IdentifyTool(Tool):  class IdentifyTool(Tool):
216    
217      """The "Identify" Tool"""      """The "Identify" Tool"""
218        
219      def Name(self):      def Name(self):
220          return "IdentifyTool"          return "IdentifyTool"
221    
# Line 191  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 215  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):  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        # 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):      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()
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
313            # coordinates to window coordinates.
314          self.scale = 1.0          self.scale = 1.0
315          self.offset = (0, 0)          self.offset = (0, 0)
316    
317            # whether the user is currently dragging the mouse, i.e. moving
318            # the mouse while pressing a mouse button
319          self.dragging = 0          self.dragging = 0
320    
321            # the currently active tool
322          self.tool = None          self.tool = None
323          self.redraw_on_idle = 0  
324            # The current mouse position of the last OnMotion event or None
325            # if the mouse is outside the window.
326            self.current_position = None
327    
328            # the bitmap serving as backing store
329            self.bitmap = None
330    
331            # the selection
332            self.selection = Selection()
333            self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
334    
335            # keep track of which layers/shapes are selected to make sure we
336            # only redraw when necessary
337            self.last_selected_layer = None
338            self.last_selected_shape = None
339    
340            self.backgroundColor = wx.wxWHITE_BRUSH
341    
342            # 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)
345          EVT_LEFT_UP(self, self.OnLeftUp)          EVT_LEFT_UP(self, self.OnLeftUp)
346          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
347            EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
348            wx.EVT_SIZE(self, self.OnSize)
349          wx.EVT_IDLE(self, self.OnIdle)          wx.EVT_IDLE(self, self.OnIdle)
350          import main  
351          self.interactor = main.app.interactor      def __del__(self):
352          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)          wxWindow.__del__(self)
353            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)
         if self.map is None or not self.map.HasLayers():  
             return  
         self.redraw_on_idle = 1  
388    
389      def do_redraw(self):          if self.map is not None and self.map.HasLayers():
390          width, height = self.GetSizeTuple()              if self.bitmap in (None, -1):
391          bitmap = wx.wxEmptyBitmap(width, height)                  # set the flag that we should redraw the
392                    # bitmap in idle time
393                    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:
401                # If we've got no map or if the map is empty, simply clear
402                # the screen.
403    
404          dc = wx.wxMemoryDC()              # XXX it's probably possible to get rid of this. The
405          dc.SelectObject(bitmap)              # background color of the window is already white and the
406                # only thing we may have to do is to call self.Refresh()
407                # with a true argument in the right places.
408                dc.BeginDrawing()
409                dc.SetBackground(self.backgroundColor)
410                dc.Clear()
411                dc.EndDrawing()
412    
413          dc.BeginDrawing()      def OnIdle(self, event):
414            # render the screen if necessary
415    
416          dc.SetBrush(wx.wxWHITE_BRUSH)          if self.bitmap != -1:
417          dc.SetPen(wx.wxTRANSPARENT_PEN)              return
         dc.DrawRectangle(0, 0, width, height)  
   
         if 1: #self.interactor.selected_map is self.map:  
             selected_layer = self.interactor.selected_layer  
             selected_shape = self.interactor.selected_shape  
         else:  
             selected_layer = None  
             selected_shape = None  
               
         renderer = ScreenRenderer(dc, self.scale, self.offset)  
         renderer.RenderMap(self.map, selected_layer, selected_shape)  
   
         clientdc = wxClientDC(self)  
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
418    
419            wxBeginBusyCursor()
420            try:
421                width, height = self.GetSizeTuple()
422    
423                bitmap = wx.wxEmptyBitmap(width, height)
424                dc = wx.wxMemoryDC()
425                dc.SelectObject(bitmap)
426                dc.BeginDrawing()
427    
428                dc.SetBackground(self.backgroundColor)
429                dc.Clear()
430    
431                selected_layer = self.selection.SelectedLayer()
432                selected_shapes = self.selection.SelectedShapes()
433    
434                # draw the map into the bitmap
435                renderer = ScreenRenderer(dc, self.scale, self.offset)
436    
437                # Pass the entire bitmap as update region to the renderer.
438                # We're redrawing the whole bitmap, after all.
439                renderer.RenderMap(self.map, (0, 0, width, height),
440                                   selected_layer, selected_shapes)
441    
442                dc.EndDrawing()
443                dc.SelectObject(wx.wxNullBitmap)
444    
445                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                renderer = ExportRenderer(dc, scale, offset)
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          printout.Destroy()          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()
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.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.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
522            # by FitMapToWindow but if map is empty it hasn't been called
523            # yet so we have to explicitly call it.
524            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 projection_changed(self, *args):      def full_redraw(self, *args):
534          self.FitMapToWindow()          self.bitmap = None
535          self.redraw()          self.redraw()
536    
537        def map_projection_changed(self, *args):
538    
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()
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          self.redraw()          # determine new offset to preserve the center
605            self.offset = (wwidth/2 - scale * pcenterx,
606                           wheight/2 + scale * pcentery)
607            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 332  class MapCanvas(wxWindow): Line 621  class MapCanvas(wxWindow):
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:
639                # zero width or zero height. Do Nothing
640                return
641          scalex = width / (urx - llx)          scalex = width / (urx - llx)
642          scaley = height / (ury - lly)          scaley = height / (ury - lly)
643          scale = min(scalex, scaley)          scale = min(scalex, scaley)
# Line 346  class MapCanvas(wxWindow): Line 646  class MapCanvas(wxWindow):
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      def ZoomFactor(self, factor):          bbox = layer.ShapesBoundingBox(shapes)
680          width, height = self.GetSizeTuple()          if bbox is not None:
681          scale = self.scale * factor              proj = self.map.GetProjection()
682          offx, offy = self.offset              if proj is not None:
683          offset = (factor * (offx - width / 2) + width / 2,                  bbox = proj.ForwardBBox(bbox)
684                    factor * (offy - height / 2) + height / 2)  
685          self.set_view_transform(scale, offset)              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):
692            """Multiply the zoom by factor and center on center.
693    
694            The optional parameter center is a point in window coordinates
695            that should be centered. If it is omitted, it defaults to the
696            center of the window
697            """
698            if self.scale > 0:
699                width, height = self.GetSizeTuple()
700                scale = self.scale * factor
701                offx, offy = self.offset
702                if center is not None:
703                    cx, cy = center
704                else:
705                    cx = width / 2
706                    cy = height / 2
707                offset = (factor * (offx - cx) + width / 2,
708                        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 381  class MapCanvas(wxWindow): Line 729  class MapCanvas(wxWindow):
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):
777            """Return current position of the mouse in projected coordinates.
778    
779            The result is a 2-tuple of floats with the coordinates. If the
780            mouse is not in the window, the result is None.
781            """
782            if self.current_position is not None:
783                x, y = self.current_position
784                return self.win_to_proj(x, y)
785            else:
786                return None
787    
788        def set_current_position(self, event):
789            """Set the current position from event
790    
791            Should be called by all events that contain mouse positions
792            especially EVT_MOTION. The event paramete may be None to
793            indicate the the pointer left the window.
794            """
795            if event is not None:
796                self.current_position = (event.m_x, event.m_y)
797            else:
798                self.current_position = None
799            self.issue(VIEW_POSITION)
800    
801      def OnLeftDown(self, event):      def OnLeftDown(self, event):
802            self.set_current_position(event)
803          if self.tool is not None:          if self.tool is not None:
804              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
805              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
# Line 411  class MapCanvas(wxWindow): Line 808  class MapCanvas(wxWindow):
808              self.tool.MouseDown(event)              self.tool.MouseDown(event)
809              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
810              self.dragging = 1              self.dragging = 1
811            
812      def OnLeftUp(self, event):      def OnLeftUp(self, event):
813          self.ReleaseMouse()          self.set_current_position(event)
814          if self.dragging:          if self.dragging:
815              self.tool.Hide(self.drag_dc)              self.ReleaseMouse()
816              self.tool.MouseUp(event)              try:
817              self.drag_dc = None                  self.tool.Hide(self.drag_dc)
818          self.dragging = 0                  self.tool.MouseUp(event)
819                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)
825          if self.dragging:          if self.dragging:
826              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
827              self.tool.MouseMove(event)              self.tool.MouseMove(event)
828              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
829    
830      def OnIdle(self, event):      def OnLeaveWindow(self, event):
831          if self.redraw_on_idle:          self.set_current_position(None)
832              self.do_redraw()  
833          self.redraw_on_idle = 0      def OnSize(self, event):
834            # 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
836            # everything away. E.g. when the window gets smaller, we could
837            # either keep the bitmap or create the new one from the old one.
838            # Even when the window becomes larger some parts of the bitmap
839            # could be reused.
840            self.full_redraw()
841            pass
842    
843      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
844          self.redraw()          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
845            # The selection object takes care that it only issues
846            # SHAPES_SELECTED messages when the set of selected shapes has
847            # actually changed, so we can do a full redraw unconditionally.
848            # FIXME: We should perhaps try to limit the redraw to the are
849            # actually covered by the shapes before and after the selection
850            # change.
851            self.full_redraw()
852    
853        def unprojected_rect_around_point(self, x, y, dist):
854            """return a rect dist pixels around (x, y) in unprojected corrdinates
855    
856      def find_shape_at(self, px, py, select_labels = 0):          The return value is a tuple (minx, miny, maxx, maxy) suitable a
857          """Return a tuple shape at point px, py in window coords."""          parameter to a layer's ShapesInRegion method.
858            """
859            map_proj = self.map.projection
860            if map_proj is not None:
861                inverse = map_proj.Inverse
862            else:
863                inverse = None
864    
865            xs = []
866            ys = []
867            for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
868                px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
869                if inverse:
870                    px, py = inverse(px, py)
871                xs.append(px)
872                ys.append(py)
873            return (min(xs), min(ys), max(xs), max(ys))
874    
875        def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
876            """Determine the shape at point px, py in window coords
877    
878            Return the shape and the corresponding layer as a tuple (layer,
879            shape).
880    
881            If the optional parameter select_labels is true (default false)
882            search through the labels. If a label is found return it's index
883            as the shape and None as the layer.
884    
885            If the optional parameter searched_layer is given (or not None
886            which it defaults to), only search in that layer.
887            """
888          map_proj = self.map.projection          map_proj = self.map.projection
889          if map_proj is not None:          if map_proj is not None:
890              forward = map_proj.Forward              forward = map_proj.Forward
# Line 443  class MapCanvas(wxWindow): Line 892  class MapCanvas(wxWindow):
892              forward = None              forward = None
893    
894          scale = self.scale          scale = self.scale
895    
896            if scale == 0:
897                return None, None
898    
899          offx, offy = self.offset          offx, offy = self.offset
900    
901          if select_labels:          if select_labels:
902              labels = self.map.LabelLayer().Labels()              labels = self.map.LabelLayer().Labels()
903                
904              if labels:              if labels:
905                  dc = wxClientDC(self)                  dc = wxClientDC(self)
906                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
907                  dc.SetFont(font)                  dc.SetFont(font)
908                  for i in range(len(labels)):                  for i in range(len(labels) - 1, -1, -1):
909                      label = labels[i]                      label = labels[i]
910                      x = label.x                      x = label.x
911                      y = label.y                      y = label.y
# Line 478  class MapCanvas(wxWindow): Line 931  class MapCanvas(wxWindow):
931                          y = y - height/2                          y = y - height/2
932                      if x <= px < x + width and y <= py <= y + height:                      if x <= px < x + width and y <= py <= y + height:
933                          return None, i                          return None, i
934                    
935          layers = self.map.Layers()          if searched_layer:
936                layers = [searched_layer]
937            else:
938                layers = self.map.Layers()
939    
940          for layer_index in range(len(layers) - 1, -1, -1):          for layer_index in range(len(layers) - 1, -1, -1):
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:
954                  inverse = layer_proj.Inverse                  inverse = layer_proj.Inverse
955              else:              else:
956                  inverse = None                  inverse = None
957                    
958              shapetype = layer.ShapeType()              shapetype = layer.ShapeType()
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)
973                shape_ids.reverse()
974    
975              if shapetype == SHAPETYPE_POLYGON:              if shapetype == SHAPETYPE_POLYGON:
976                  for i in range(layer.NumShapes()):                  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 511  class MapCanvas(wxWindow): Line 984  class MapCanvas(wxWindow):
984                          select_shape = i                          select_shape = i
985                          break                          break
986              elif shapetype == SHAPETYPE_ARC:              elif shapetype == SHAPETYPE_ARC:
987                  for i in range(layer.NumShapes()):                  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 521  class MapCanvas(wxWindow): Line 995  class MapCanvas(wxWindow):
995                          select_shape = i                          select_shape = i
996                          break                          break
997              elif shapetype == SHAPETYPE_POINT:              elif shapetype == SHAPETYPE_POINT:
998                  for i in range(layer.NumShapes()):                  for i in shape_ids:
999                      shape = layer.Shape(i)                      shape = layer.Shape(i)
1000                      x, y = shape.Points()[0]                      x, y = shape.Points()[0]
1001                      if inverse:                      if inverse:
# Line 538  class MapCanvas(wxWindow): Line 1012  class MapCanvas(wxWindow):
1012                  return layer, select_shape                  return layer, select_shape
1013          return None, None          return None, None
1014    
1015      def SelectShapeAt(self, x, y):      def SelectShapeAt(self, x, y, layer = None):
1016          layer, shape = self.find_shape_at(x, y)          """\
1017          self.interactor.SelectLayerAndShape(layer, shape)          Select and return the shape and its layer at window position (x, y)
1018    
1019            If layer is given, only search in that layer. If no layer is
1020            given, search through all layers.
1021    
1022            Return a tuple (layer, shapeid). If no shape is found, return
1023            (None, None).
1024            """
1025            layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
1026            # If layer is None, then shape will also be None. We don't want
1027            # to deselect the currently selected layer, so we simply select
1028            # the already selected layer again.
1029            if layer is None:
1030                layer = self.selection.SelectedLayer()
1031                shapes = []
1032            else:
1033                shapes = [shape]
1034            self.selection.SelectShapes(layer, shapes)
1035            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 550  class MapCanvas(wxWindow): Line 1048  class MapCanvas(wxWindow):
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 565  class MapCanvas(wxWindow): Line 1065  class MapCanvas(wxWindow):
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 591  class MapCanvas(wxWindow): Line 1091  class MapCanvas(wxWindow):
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.6  
changed lines
  Added in v.1344

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26