"""! @package gcp.mapdisplay @brief Display to manage ground control points with two toolbars, one for various display management functions, one for manipulating GCPs. Classes: - mapdisplay::MapFrame (C) 2006-2011 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Markus Metz """ import os import math import platform from core import globalvar import wx import wx.aui from core.render import EVT_UPDATE_PRGBAR from mapdisp.toolbars import MapToolbar from gcp.toolbars import GCPDisplayToolbar, GCPManToolbar from mapdisp.gprint import PrintOptions from core.gcmd import GMessage from gui_core.dialogs import GetImageHandlers, ImageSizeDialog from gui_core.mapdisp import SingleMapFrame from core.settings import UserSettings from mapdisp.mapwindow import BufferedWindow import mapdisp.statusbar as sb # for standalone app cmdfilename = None class MapFrame(SingleMapFrame): """!Main frame for map display window. Drawing takes place in child double buffered drawing window. """ def __init__(self, parent=None, title=_("GRASS GIS Manage Ground Control Points"), toolbars=["gcpdisp"], tree=None, notebook=None, lmgr=None, page=None, Map=None, auimgr=None, name = 'GCPMapWindow', **kwargs): """!Main map display window with toolbars, statusbar and DrawWindow @param toolbars array of activated toolbars, e.g. ['map', 'digit'] @param tree reference to layer tree @param notebook control book ID in Layer Manager @param lmgr Layer Manager @param page notebook page with layer tree @param Map instance of render.Map @param auimgs AUI manager @param kwargs wx.Frame attribures """ SingleMapFrame.__init__(self, parent = parent, title = title, Map = Map, auimgr = auimgr, name = name, **kwargs) self._layerManager = lmgr # Layer Manager object self.tree = tree # Layer Manager layer tree object self.page = page # Notebook page holding the layer tree self.layerbook = notebook # Layer Manager layer tree notebook # # Add toolbars # for toolb in toolbars: self.AddToolbar(toolb) self.activemap = self.toolbars['gcpdisp'].togglemap self.activemap.SetSelection(0) self.SrcMap = self.grwiz.SrcMap # instance of render.Map self.TgtMap = self.grwiz.TgtMap # instance of render.Map self._mgr.SetDockSizeConstraint(0.5, 0.5) # # Add statusbar # # items for choice self.statusbarItems = [sb.SbCoordinates, sb.SbRegionExtent, sb.SbCompRegionExtent, sb.SbShowRegion, sb.SbResolution, sb.SbDisplayGeometry, sb.SbMapScale, sb.SbProjection, sb.SbGoToGCP, sb.SbRMSError] # create statusbar and its manager statusbar = self.CreateStatusBar(number = 4, style = 0) statusbar.SetStatusWidths([-5, -2, -1, -1]) self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar) # fill statusbar manager self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar) self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2)) self.statusbarManager.AddStatusbarItem(sb.SbRender(self, statusbar = statusbar, position = 3)) self.statusbarManager.SetMode(8) # goto GCP self.statusbarManager.Update() # # Init map display (buffered DC & set default cursor) # self.grwiz.SwitchEnv('source') self.SrcMapWindow = BufferedWindow(self, id=wx.ID_ANY, Map=self.SrcMap, tree=self.tree, lmgr=self._layerManager) self.grwiz.SwitchEnv('target') self.TgtMapWindow = BufferedWindow(self, id=wx.ID_ANY, Map=self.TgtMap, tree=self.tree, lmgr=self._layerManager) self.MapWindow = self.SrcMapWindow self.Map = self.SrcMap self.SrcMapWindow.SetCursor(self.cursors["cross"]) self.TgtMapWindow.SetCursor(self.cursors["cross"]) # # initialize region values # self._initMap(Map = self.SrcMap) self._initMap(Map = self.TgtMap) # # Bind various events # self.Bind(EVT_UPDATE_PRGBAR, self.OnUpdateProgress) self.activemap.Bind(wx.EVT_CHOICE, self.OnUpdateActive) # # Update fancy gui style # # AuiManager wants a CentrePane, workaround to get two equally sized windows self.list = self.CreateGCPList() #self.SrcMapWindow.SetSize((300, 300)) #self.TgtMapWindow.SetSize((300, 300)) self.list.SetSize((100, 150)) self._mgr.AddPane(self.list, wx.aui.AuiPaneInfo(). Name("gcplist").Caption(_("GCP List")).LeftDockable(False). RightDockable(False).PinButton().FloatingSize((600,200)). CloseButton(False).DestroyOnClose(True). Top().Layer(1).MinSize((200,100))) self._mgr.AddPane(self.SrcMapWindow, wx.aui.AuiPaneInfo(). Name("source").Caption(_("Source Display")).Dockable(False). CloseButton(False).DestroyOnClose(True).Floatable(False). Centre()) self._mgr.AddPane(self.TgtMapWindow, wx.aui.AuiPaneInfo(). Name("target").Caption(_("Target Display")).Dockable(False). CloseButton(False).DestroyOnClose(True).Floatable(False). Right().Layer(0)) srcwidth, srcheight = self.SrcMapWindow.GetSize() tgtwidth, tgtheight = self.TgtMapWindow.GetSize() srcwidth = (srcwidth + tgtwidth) / 2 self._mgr.GetPane("target").Hide() self._mgr.Update() self._mgr.GetPane("source").BestSize((srcwidth, srcheight)) self._mgr.GetPane("target").BestSize((srcwidth, srcheight)) if self.show_target: self._mgr.GetPane("target").Show() else: self.activemap.Enable(False) # needed by Mac OS, does not harm on Linux, breaks display on Windows if platform.system() != 'Windows': self._mgr.Update() # # Init print module and classes # self.printopt = PrintOptions(self, self.MapWindow) # # Initialization of digitization tool # self.digit = None # set active map self.MapWindow = self.SrcMapWindow self.Map = self.SrcMap # do not init zoom history here, that happens when zooming to map(s) # # Re-use dialogs # self.dialogs = {} self.dialogs['attributes'] = None self.dialogs['category'] = None self.dialogs['barscale'] = None self.dialogs['legend'] = None self.decorationDialog = None # decoration/overlays def AddToolbar(self, name): """!Add defined toolbar to the window Currently known toolbars are: - 'map' - basic map toolbar - 'vdigit' - vector digitizer - 'gcpdisp' - GCP Manager, Display - 'gcpman' - GCP Manager, points management - 'nviz' - 3D view mode """ # default toolbar if name == "map": self.toolbars['map'] = MapToolbar(self, self.Map) self._mgr.AddPane(self.toolbars['map'], wx.aui.AuiPaneInfo(). Name("maptoolbar").Caption(_("Map Toolbar")). ToolbarPane().Top(). LeftDockable(False).RightDockable(False). BottomDockable(False).TopDockable(True). CloseButton(False).Layer(2). BestSize((self.toolbars['map'].GetSize()))) # GCP display elif name == "gcpdisp": self.toolbars['gcpdisp'] = GCPDisplayToolbar(self) self._mgr.AddPane(self.toolbars['gcpdisp'], wx.aui.AuiPaneInfo(). Name("gcpdisplaytoolbar").Caption(_("GCP Display toolbar")). ToolbarPane().Top(). LeftDockable(False).RightDockable(False). BottomDockable(False).TopDockable(True). CloseButton(False).Layer(2)) if self.show_target == False: self.toolbars['gcpdisp'].Enable('zoommenu', enable = False) self.toolbars['gcpman'] = GCPManToolbar(self) self._mgr.AddPane(self.toolbars['gcpman'], wx.aui.AuiPaneInfo(). Name("gcpmanagertoolbar").Caption(_("GCP Manager toolbar")). ToolbarPane().Top().Row(1). LeftDockable(False).RightDockable(False). BottomDockable(False).TopDockable(True). CloseButton(False).Layer(2)) self._mgr.Update() def OnUpdateProgress(self, event): """ Update progress bar info """ self.GetProgressBar().SetValue(event.value) event.Skip() def OnFocus(self, event): """ Change choicebook page to match display. Or set display for georectifying """ if self._layerManager and \ self._layerManager.gcpmanagement: # in GCP Management, set focus to current MapWindow for mouse actions self.OnPointer(event) self.MapWindow.SetFocus() else: # change bookcontrol page to page associated with display # GCP Manager: use bookcontrol? if self.page: pgnum = self.layerbook.GetPageIndex(self.page) if pgnum > -1: self.layerbook.SetSelection(pgnum) event.Skip() def OnDraw(self, event): """!Re-display current map composition """ self.MapWindow.UpdateMap(render = False) def OnRender(self, event): """!Re-render map composition (each map layer) """ # delete tmp map layers (queries) qlayer = self.Map.GetListOfLayers(l_name=globalvar.QUERYLAYER) for layer in qlayer: self.Map.DeleteLayer(layer) self.SrcMapWindow.UpdateMap(render=True) if self.show_target: self.TgtMapWindow.UpdateMap(render=True) # update statusbar self.StatusbarUpdate() def OnPointer(self, event): """!Pointer button clicked """ # change the cursor self.SrcMapWindow.SetCursor(self.cursors["cross"]) self.SrcMapWindow.mouse['use'] = "pointer" self.SrcMapWindow.mouse['box'] = "point" self.TgtMapWindow.SetCursor(self.cursors["cross"]) self.TgtMapWindow.mouse['use'] = "pointer" self.TgtMapWindow.mouse['box'] = "point" def OnZoomIn(self, event): """ Zoom in the map. Set mouse cursor, zoombox attributes, and zoom direction """ self.toolbars['gcpdisp'].OnTool(event) self.toolbars['gcpdisp'].action['desc'] = '' self.MapWindow.mouse['use'] = "zoom" self.MapWindow.mouse['box'] = "box" self.MapWindow.zoomtype = 1 self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) # change the cursor self.MapWindow.SetCursor(self.cursors["cross"]) if self.MapWindow == self.SrcMapWindow: win = self.TgtMapWindow elif self.MapWindow == self.TgtMapWindow: win = self.SrcMapWindow win.mouse['use'] = "zoom" win.mouse['box'] = "box" win.zoomtype = 1 win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) # change the cursor win.SetCursor(self.cursors["cross"]) def OnZoomOut(self, event): """ Zoom out the map. Set mouse cursor, zoombox attributes, and zoom direction """ self.toolbars['gcpdisp'].OnTool(event) self.toolbars['gcpdisp'].action['desc'] = '' self.MapWindow.mouse['use'] = "zoom" self.MapWindow.mouse['box'] = "box" self.MapWindow.zoomtype = -1 self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) # change the cursor self.MapWindow.SetCursor(self.cursors["cross"]) if self.MapWindow == self.SrcMapWindow: win = self.TgtMapWindow elif self.MapWindow == self.TgtMapWindow: win = self.SrcMapWindow win.mouse['use'] = "zoom" win.mouse['box'] = "box" win.zoomtype = -1 win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) # change the cursor win.SetCursor(self.cursors["cross"]) def OnPan(self, event): """ Panning, set mouse to drag """ self.toolbars['gcpdisp'].OnTool(event) self.toolbars['gcpdisp'].action['desc'] = '' self.MapWindow.mouse['use'] = "pan" self.MapWindow.mouse['box'] = "pan" self.MapWindow.zoomtype = 0 # change the cursor self.MapWindow.SetCursor(self.cursors["hand"]) if self.MapWindow == self.SrcMapWindow: win = self.TgtMapWindow elif self.MapWindow == self.TgtMapWindow: win = self.SrcMapWindow win.mouse['use'] = "pan" win.mouse['box'] = "pan" win.zoomtype = 0 # change the cursor win.SetCursor(self.cursors["hand"]) def OnErase(self, event): """ Erase the canvas """ self.MapWindow.EraseMap() if self.MapWindow == self.SrcMapWindow: win = self.TgtMapWindow elif self.MapWindow == self.TgtMapWindow: win = self.SrcMapWindow win.EraseMap() def OnZoomRegion(self, event): """ Zoom to region """ self.Map.getRegion() self.Map.getResolution() self.UpdateMap() # event.Skip() def OnAlignRegion(self, event): """ Align region """ if not self.Map.alignRegion: self.Map.alignRegion = True else: self.Map.alignRegion = False # event.Skip() def SaveToFile(self, event): """!Save map to image """ img = self.MapWindow.img if not img: GMessage(parent = self, message = _("Nothing to render (empty map). Operation canceled.")) return filetype, ltype = GetImageHandlers(img) # get size dlg = ImageSizeDialog(self) dlg.CentreOnParent() if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return width, height = dlg.GetValues() dlg.Destroy() # get filename dlg = wx.FileDialog(parent = self, message = _("Choose a file name to save the image " "(no need to add extension)"), wildcard = filetype, style=wx.SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() if not path: dlg.Destroy() return base, ext = os.path.splitext(path) fileType = ltype[dlg.GetFilterIndex()]['type'] extType = ltype[dlg.GetFilterIndex()]['ext'] if ext != extType: path = base + '.' + extType self.MapWindow.SaveToFile(path, fileType, width, height) dlg.Destroy() def PrintMenu(self, event): """ Print options and output menu for map display """ point = wx.GetMousePosition() printmenu = wx.Menu() # Add items to the menu setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup')) printmenu.AppendItem(setup) self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup) preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview')) printmenu.AppendItem(preview) self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview) doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display')) printmenu.AppendItem(doprint) self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint) # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(printmenu) printmenu.Destroy() def FormatDist(self, dist): """!Format length numbers and units in a nice way, as a function of length. From code by Hamish Bowman Grass Development Team 2006""" mapunits = self.Map.projinfo['units'] if mapunits == 'metres': mapunits = 'meters' outunits = mapunits dist = float(dist) divisor = 1.0 # figure out which units to use if mapunits == 'meters': if dist > 2500.0: outunits = 'km' divisor = 1000.0 else: outunits = 'm' elif mapunits == 'feet': # nano-bug: we match any "feet", but US Survey feet is really # 5279.9894 per statute mile, or 10.6' per 1000 miles. As >1000 # miles the tick markers are rounded to the nearest 10th of a # mile (528'), the difference in foot flavours is ignored. if dist > 5280.0: outunits = 'miles' divisor = 5280.0 else: outunits = 'ft' elif 'degree' in mapunits: if dist < 1: outunits = 'min' divisor = (1/60.0) else: outunits = 'deg' # format numbers in a nice way if (dist/divisor) >= 2500.0: outdist = round(dist/divisor) elif (dist/divisor) >= 1000.0: outdist = round(dist/divisor,1) elif (dist/divisor) > 0.0: outdist = round(dist/divisor,int(math.ceil(3-math.log10(dist/divisor)))) else: outdist = float(dist/divisor) return (outdist, outunits) def OnZoomToRaster(self, event): """! Set display extents to match selected raster map (ignore NULLs) """ self.MapWindow.ZoomToMap(ignoreNulls = True) def OnZoomToSaved(self, event): """!Set display geometry to match extents in saved region file """ self.MapWindow.ZoomToSaved() def OnDisplayToWind(self, event): """!Set computational region (WIND file) to match display extents """ self.MapWindow.DisplayToWind() def SaveDisplayRegion(self, event): """!Save display extents to named region file. """ self.MapWindow.SaveDisplayRegion() def OnZoomMenu(self, event): """!Popup Zoom menu """ point = wx.GetMousePosition() zoommenu = wx.Menu() # Add items to the menu zoomwind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to computational region (set with g.region)')) zoommenu.AppendItem(zoomwind) self.Bind(wx.EVT_MENU, self.OnZoomToWind, zoomwind) zoomdefault = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to default region')) zoommenu.AppendItem(zoomdefault) self.Bind(wx.EVT_MENU, self.OnZoomToDefault, zoomdefault) zoomsaved = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to saved region')) zoommenu.AppendItem(zoomsaved) self.Bind(wx.EVT_MENU, self.OnZoomToSaved, zoomsaved) savewind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Set computational region from display')) zoommenu.AppendItem(savewind) self.Bind(wx.EVT_MENU, self.OnDisplayToWind, savewind) savezoom = wx.MenuItem(zoommenu, wx.ID_ANY, _('Save display geometry to named region')) zoommenu.AppendItem(savezoom) self.Bind(wx.EVT_MENU, self.SaveDisplayRegion, savezoom) # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(zoommenu) zoommenu.Destroy() def IsStandalone(self): """!Check if Map display is standalone""" if self._layerManager: return False return True def GetLayerManager(self): """!Get reference to Layer Manager @return window reference @return None (if standalone) """ return self._layerManager def GetSrcWindow(self): return self.SrcMapWindow def GetTgtWindow(self): return self.TgtMapWindow def GetShowTarget(self): return self.show_target def GetMapToolbar(self): """!Returns toolbar with zooming tools""" return self.toolbars['gcpdisp']