1""" 2@package mapdisp.frame 3 4@brief Map display with toolbar for various display management 5functions, and additional toolbars (vector digitizer, 3d view). 6 7Can be used either from Layer Manager or as d.mon backend. 8 9Classes: 10 - mapdisp::MapFrame 11 12(C) 2006-2016 by the GRASS Development Team 13 14This program is free software under the GNU General Public License 15(>=v2). Read the file COPYING that comes with GRASS for details. 16 17@author Michael Barton 18@author Jachym Cepicky 19@author Martin Landa <landa.martin gmail.com> 20@author Vaclav Petras <wenzeslaus gmail.com> (SingleMapFrame, handlers support) 21@author Anna Kratochvilova <kratochanna gmail.com> (SingleMapFrame) 22@author Stepan Turek <stepan.turek seznam.cz> (handlers support) 23""" 24 25import os 26import sys 27import copy 28 29from core import globalvar 30import wx 31import wx.aui 32 33from mapdisp.toolbars import MapToolbar, NvizIcons 34from mapdisp.gprint import PrintOptions 35from core.gcmd import GError, GMessage, RunCommand 36from core.utils import ListOfCatsToRange, GetLayerNameFromCmd 37from gui_core.dialogs import GetImageHandlers, ImageSizeDialog 38from core.debug import Debug 39from core.settings import UserSettings 40from gui_core.mapdisp import SingleMapFrame 41from mapwin.base import MapWindowProperties 42from gui_core.query import QueryDialog, PrepareQueryResults 43from mapwin.buffered import BufferedMapWindow 44from mapwin.decorations import LegendController, BarscaleController, \ 45 ArrowController, DtextController, LegendVectController 46from mapwin.analysis import ProfileController, MeasureDistanceController, \ 47 MeasureAreaController 48from gui_core.forms import GUI 49from core.giface import Notification 50from gui_core.vselect import VectorSelectBase, VectorSelectHighlighter 51from gui_core.wrap import Menu 52from mapdisp import statusbar as sb 53 54import grass.script as grass 55 56from grass.pydispatch.signal import Signal 57 58 59class MapFrame(SingleMapFrame): 60 """Main frame for map display window. Drawing takes place in 61 child double buffered drawing window. 62 """ 63 64 def __init__(self, parent, giface, title=_("GRASS GIS - Map display"), 65 toolbars=["map"], statusbar=True, 66 tree=None, notebook=None, lmgr=None, 67 page=None, Map=None, auimgr=None, name='MapWindow', **kwargs): 68 """Main map display window with toolbars, statusbar and 69 2D map window, 3D map window and digitizer. 70 71 :param toolbars: array of activated toolbars, e.g. ['map', 'digit'] 72 :param statusbar: True to add statusbar 73 :param tree: reference to layer tree 74 :param notebook: control book ID in Layer Manager 75 :param lmgr: Layer Manager 76 :param page: notebook page with layer tree 77 :param map: instance of render.Map 78 :param auimgr: AUI manager 79 :param name: frame name 80 :param kwargs: wx.Frame attributes 81 """ 82 SingleMapFrame.__init__(self, parent=parent, title=title, 83 Map=Map, auimgr=auimgr, name=name, **kwargs) 84 85 self._giface = giface 86 # Layer Manager object 87 # need by GLWindow (a lot), VDigitWindow (a little bit) 88 self._layerManager = lmgr 89 # Layer Manager layer tree object 90 # used for VDigit toolbar and window and GLWindow 91 self.tree = tree 92 # Notebook page holding the layer tree 93 # used only in OnCloseWindow 94 self.page = page 95 # Layer Manager layer tree notebook 96 # used only in OnCloseWindow 97 self.layerbook = notebook 98 99 # Emitted when starting (switching to) 3D mode. 100 # Parameter firstTime specifies if 3D was already actived. 101 self.starting3dMode = Signal("MapFrame.starting3dMode") 102 103 # Emitted when ending (switching from) 3D mode. 104 self.ending3dMode = Signal("MapFrame.ending3dMode") 105 106 # Emitted when closing display by closing its window. 107 self.closingDisplay = Signal("MapFrame.closingDisplay") 108 109 # Emitted when closing display by closing its window. 110 self.closingVNETDialog = Signal("MapFrame.closingVNETDialog") 111 112 # properties are shared in other objects, so defining here 113 self.mapWindowProperties = MapWindowProperties() 114 self.mapWindowProperties.setValuesFromUserSettings() 115 116 # 117 # Add toolbars 118 # 119 for toolb in toolbars: 120 self.AddToolbar(toolb) 121 122 # 123 # Add statusbar 124 # 125 self.statusbarManager = None 126 if statusbar: 127 self.CreateStatusbar() 128 129 # init decoration objects 130 self.decorations = {} 131 self._decorationWindows = {} 132 133 self.mapWindowProperties.autoRenderChanged.connect( 134 lambda value: 135 self.OnRender(None) if value else None) 136 137 # 138 # Init map display (buffered DC & set default cursor) 139 # 140 self.MapWindow2D = BufferedMapWindow( 141 self, giface=self._giface, Map=self.Map, 142 properties=self.mapWindowProperties, overlays=self.decorations) 143 self.MapWindow2D.mapQueried.connect(self.Query) 144 self.MapWindow2D.overlayActivated.connect(self._activateOverlay) 145 self.MapWindow2D.overlayRemoved.connect(self.RemoveOverlay) 146 self._setUpMapWindow(self.MapWindow2D) 147 148 self.MapWindow2D.mouseHandlerUnregistered.connect(self.ResetPointer) 149 150 self.MapWindow2D.InitZoomHistory() 151 self.MapWindow2D.zoomChanged.connect(self.StatusbarUpdate) 152 153 self._giface.updateMap.connect(self.MapWindow2D.UpdateMap) 154 # default is 2D display mode 155 self.MapWindow = self.MapWindow2D 156 self.MapWindow.SetNamedCursor('default') 157 # used by vector digitizer 158 self.MapWindowVDigit = None 159 # used by Nviz (3D display mode) 160 self.MapWindow3D = None 161 162 if 'map' in self.toolbars: 163 self.toolbars['map'].SelectDefault() 164 165 # 166 # Bind various events 167 # 168 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 169 self.Bind(wx.EVT_SIZE, self.OnSize) 170 171 # 172 # Update fancy gui style 173 # 174 self._mgr.AddPane(self.MapWindow, wx.aui.AuiPaneInfo().CentrePane(). 175 Dockable(False).BestSize((-1, -1)).Name('2d'). 176 CloseButton(False).DestroyOnClose(True). 177 Layer(0)) 178 self._mgr.Update() 179 180 # 181 # Init print module and classes 182 # 183 self.printopt = PrintOptions(self, self.MapWindow) 184 185 # 186 # Re-use dialogs 187 # 188 self.dialogs = {} 189 self.dialogs['attributes'] = None 190 self.dialogs['category'] = None 191 self.dialogs['vnet'] = None 192 self.dialogs['query'] = None 193 self.dialogs['vselect'] = None 194 195 # initialize layers to query (d.what.vect/rast) 196 self._vectQueryLayers = [] 197 self._rastQueryLayers = [] 198 # initialize highlighter for vector features 199 self._highlighter_layer = None 200 201 self.measureController = None 202 203 self._resize() 204 205 def CreateStatusbar(self): 206 if self.statusbarManager: 207 return 208 209 # items for choice 210 self.statusbarItems = [sb.SbCoordinates, 211 sb.SbRegionExtent, 212 sb.SbCompRegionExtent, 213 sb.SbShowRegion, 214 sb.SbAlignExtent, 215 sb.SbResolution, 216 sb.SbDisplayGeometry, 217 sb.SbMapScale, 218 sb.SbGoTo, 219 sb.SbProjection] 220 221 self.statusbarItemsHiddenInNviz = (sb.SbAlignExtent, 222 sb.SbDisplayGeometry, 223 sb.SbShowRegion, 224 sb.SbResolution, 225 sb.SbMapScale) 226 227 # create statusbar and its manager 228 statusbar = self.CreateStatusBar(number=4, style=0) 229 statusbar.SetMinHeight(24) 230 statusbar.SetStatusWidths([-5, -2, -1, -1]) 231 self.statusbarManager = sb.SbManager( 232 mapframe=self, statusbar=statusbar) 233 234 # fill statusbar manager 235 self.statusbarManager.AddStatusbarItemsByClass( 236 self.statusbarItems, mapframe=self, statusbar=statusbar) 237 self.statusbarManager.AddStatusbarItem( 238 sb.SbMask(self, statusbar=statusbar, position=2)) 239 sbRender = sb.SbRender(self, statusbar=statusbar, position=3) 240 self.statusbarManager.AddStatusbarItem(sbRender) 241 242 self.statusbarManager.Update() 243 244 # 245 self.Map.GetRenderMgr().updateProgress.connect(self.statusbarManager.SetProgress) 246 self.Map.GetRenderMgr().renderingFailed.connect(lambda cmd, error: self._giface.WriteError( 247 _("Failed to run command '%(command)s'. Details:\n%(error)s") % dict(command=' '.join(cmd), error=error))) 248 249 def GetMapWindow(self): 250 return self.MapWindow 251 252 def SetTitleWithName(self, name): 253 """Set map display title its name 254 255 This function should be used when there are multiple map 256 displays. 257 258 Sets also other dynamically determined parts of the title 259 specific for GRASS GIS map display, 260 while the standard (inherited) ``SetTitle()`` function sets the 261 raw title and doesn't add or modify anything. 262 """ 263 gisenv = grass.gisenv() 264 title = _("GRASS GIS Map Display: %(name)s - %(loc)s/%(mapset)s") % { 265 'name': name, 266 'loc': gisenv["LOCATION_NAME"], 267 'mapset': gisenv["MAPSET"]} 268 269 self.SetTitle(title) 270 271 def _addToolbarVDigit(self): 272 """Add vector digitizer toolbar 273 """ 274 from vdigit.main import haveVDigit, VDigit 275 from vdigit.toolbars import VDigitToolbar 276 277 if not haveVDigit: 278 from vdigit import errorMsg 279 280 self.toolbars['map'].combo.SetValue(_("2D view")) 281 282 GError(_("Unable to start wxGUI vector digitizer.\n" 283 "Details: %s") % errorMsg, parent=self) 284 return 285 286 if not self.MapWindowVDigit: 287 from vdigit.mapwindow import VDigitWindow 288 self.MapWindowVDigit = VDigitWindow( 289 parent=self, giface=self._giface, 290 properties=self.mapWindowProperties, Map=self.Map, 291 tree=self.tree, lmgr=self._layerManager, 292 overlays=self.decorations) 293 self._setUpMapWindow(self.MapWindowVDigit) 294 self.MapWindowVDigit.digitizingInfo.connect( 295 lambda text: 296 self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(text)) 297 self.MapWindowVDigit.digitizingInfoUnavailable.connect( 298 lambda: self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(None)) 299 self.MapWindowVDigit.Show() 300 self._mgr.AddPane( 301 self.MapWindowVDigit, wx.aui.AuiPaneInfo().CentrePane(). Dockable(False).BestSize( 302 (-1, -1)).Name('vdigit'). CloseButton(False).DestroyOnClose(True). Layer(0)) 303 304 self._switchMapWindow(self.MapWindowVDigit) 305 306 if self._mgr.GetPane('2d').IsShown(): 307 self._mgr.GetPane('2d').Hide() 308 elif self._mgr.GetPane('3d').IsShown(): 309 self._mgr.GetPane('3d').Hide() 310 self._mgr.GetPane('vdigit').Show() 311 if 'vdigit' not in self.toolbars: 312 self.toolbars['vdigit'] = VDigitToolbar( 313 parent=self, toolSwitcher=self._toolSwitcher, 314 MapWindow=self.MapWindow, digitClass=VDigit, 315 giface=self._giface) 316 self.toolbars['vdigit'].quitDigitizer.connect(self.QuitVDigit) 317 self.Map.layerAdded.connect(self._updateVDigitLayers) 318 self.MapWindowVDigit.SetToolbar(self.toolbars['vdigit']) 319 320 self._mgr.AddPane(self.toolbars['vdigit'], 321 wx.aui.AuiPaneInfo(). 322 Name("vdigittoolbar").Caption(_("Vector Digitizer Toolbar")). 323 ToolbarPane().Top().Row(1). 324 LeftDockable(False).RightDockable(False). 325 BottomDockable(False).TopDockable(True). 326 CloseButton(False).Layer(2). 327 BestSize((self.toolbars['vdigit'].GetBestSize()))) 328 # change mouse to draw digitized line 329 self.MapWindow.mouse['box'] = "point" 330 self.MapWindow.zoomtype = 0 331 self.MapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SOLID) 332 self.MapWindow.polypen = wx.Pen( 333 colour='green', width=2, style=wx.SOLID) 334 335 def _updateVDigitLayers(self, layer): 336 """Update vdigit layers""" 337 if 'vdigit' in self.toolbars: 338 self.toolbars['vdigit'].UpdateListOfLayers(updateTool=True) 339 340 def AddNviz(self): 341 """Add 3D view mode window 342 """ 343 from nviz.main import haveNviz, GLWindow, errorMsg 344 345 # check for GLCanvas and OpenGL 346 if not haveNviz: 347 self.toolbars['map'].combo.SetValue(_("2D view")) 348 GError( 349 parent=self, message=_( 350 "Unable to switch to 3D display mode.\nThe Nviz python extension " 351 "was not found or loaded properly.\n" 352 "Switching back to 2D display mode.\n\nDetails: %s" % 353 errorMsg)) 354 return 355 356 # here was disabling 3D for other displays, now done on starting3dMode 357 358 self.toolbars['map'].Enable2D(False) 359 # add rotate tool to map toolbar 360 self.toolbars['map'].InsertTool( 361 (('rotate', 362 NvizIcons['rotate'], 363 self.OnRotate, 364 wx.ITEM_CHECK, 365 7), 366 )) # 7 is position 367 self._toolSwitcher.AddToolToGroup( 368 group='mouseUse', toolbar=self.toolbars['map'], 369 tool=self.toolbars['map'].rotate) 370 self.toolbars['map'].InsertTool((('flyThrough', NvizIcons['flyThrough'], 371 self.OnFlyThrough, wx.ITEM_CHECK, 8),)) 372 self._toolSwitcher.AddToolToGroup( 373 group='mouseUse', toolbar=self.toolbars['map'], 374 tool=self.toolbars['map'].flyThrough) 375 # update status bar 376 377 self.statusbarManager.HideStatusbarChoiceItemsByClass( 378 self.statusbarItemsHiddenInNviz) 379 self.statusbarManager.SetMode(0) 380 381 # erase map window 382 self.MapWindow.EraseMap() 383 384 self._giface.WriteCmdLog( 385 _("Starting 3D view mode..."), 386 notification=Notification.HIGHLIGHT) 387 self.SetStatusText(_("Please wait, loading data..."), 0) 388 389 # create GL window 390 if not self.MapWindow3D: 391 self.MapWindow3D = GLWindow( 392 self, 393 giface=self._giface, 394 id=wx.ID_ANY, 395 frame=self, 396 Map=self.Map, 397 tree=self.tree, 398 lmgr=self._layerManager) 399 self._setUpMapWindow(self.MapWindow3D) 400 self.MapWindow3D.mapQueried.connect(self.Query) 401 self._switchMapWindow(self.MapWindow3D) 402 self.MapWindow.SetNamedCursor('default') 403 404 # here was AddNvizTools in lmgr 405 self.starting3dMode.emit(firstTime=True) 406 407 # switch from MapWindow to MapWindowGL 408 self._mgr.GetPane('2d').Hide() 409 self._mgr.AddPane( 410 self.MapWindow3D, wx.aui.AuiPaneInfo().CentrePane(). Dockable(False).BestSize( 411 (-1, -1)).Name('3d'). CloseButton(False).DestroyOnClose(True). Layer(0)) 412 413 self.MapWindow3D.Show() 414 self.MapWindow3D.ResetViewHistory() 415 self.MapWindow3D.UpdateView(None) 416 self.MapWindow3D.overlayActivated.connect(self._activateOverlay) 417 self.MapWindow3D.overlayRemoved.connect(self.RemoveOverlay) 418 else: 419 self._switchMapWindow(self.MapWindow3D) 420 os.environ['GRASS_REGION'] = self.Map.SetRegion( 421 windres=True, windres3=True) 422 self.MapWindow3D.GetDisplay().Init() 423 self.MapWindow3D.LoadDataLayers() 424 del os.environ['GRASS_REGION'] 425 426 # switch from MapWindow to MapWindowGL 427 self._mgr.GetPane('2d').Hide() 428 self._mgr.GetPane('3d').Show() 429 430 # here was AddNvizTools in lmgr and updating of pages 431 self.starting3dMode.emit(firstTime=False) 432 433 self.MapWindow3D.ResetViewHistory() 434 435 # connect signals for updating overlays 436 for overlay in self.decorations.values(): 437 overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays) 438 self.Map.GetRenderMgr().renderDone.connect(self.MapWindow3D._onUpdateOverlays) 439 440 self._giface.updateMap.disconnect(self.MapWindow2D.UpdateMap) 441 self._giface.updateMap.connect(self.MapWindow3D.UpdateMap) 442 self.MapWindow3D.overlays = self.MapWindow2D.overlays 443 # update overlays needs to be called after because getClientSize 444 # is called during update and it must give reasonable values 445 wx.CallAfter(self.MapWindow3D.UpdateOverlays) 446 447 self.SetStatusText("", 0) 448 self._mgr.Update() 449 450 def Disable3dMode(self): 451 """Disables 3D mode (NVIZ) in user interface.""" 452 # TODO: this is broken since item is removed but switch is drived by 453 # index 454 if '3D' in self.toolbars['map'].combo.GetString(1): 455 self.toolbars['map'].combo.Delete(1) 456 457 def RemoveNviz(self): 458 """Restore 2D view""" 459 try: 460 self.toolbars['map'].RemoveTool(self.toolbars['map'].rotate) 461 self.toolbars['map'].RemoveTool(self.toolbars['map'].flyThrough) 462 except AttributeError: 463 pass 464 465 # update status bar 466 self.statusbarManager.ShowStatusbarChoiceItemsByClass( 467 self.statusbarItemsHiddenInNviz) 468 self.statusbarManager.SetMode(UserSettings.Get(group='display', 469 key='statusbarMode', 470 subkey='selection')) 471 self.SetStatusText(_("Please wait, unloading data..."), 0) 472 # unloading messages from library cause highlight anyway 473 self._giface.WriteCmdLog(_("Switching back to 2D view mode..."), 474 notification=Notification.NO_NOTIFICATION) 475 if self.MapWindow3D: 476 self.MapWindow3D.OnClose(event=None) 477 # switch from MapWindowGL to MapWindow 478 self._mgr.GetPane('2d').Show() 479 self._mgr.GetPane('3d').Hide() 480 481 self._switchMapWindow(self.MapWindow2D) 482 # here was RemoveNvizTools form lmgr 483 self.ending3dMode.emit() 484 try: 485 self.MapWindow2D.overlays = self.MapWindow3D.overlays 486 except AttributeError: 487 pass 488 # TODO: here we end because self.MapWindow3D is None for a while 489 self._giface.updateMap.disconnect(self.MapWindow3D.UpdateMap) 490 self._giface.updateMap.connect(self.MapWindow2D.UpdateMap) 491 # disconnect overlays 492 for overlay in self.decorations.values(): 493 overlay.overlayChanged.disconnect(self.MapWindow3D.UpdateOverlays) 494 self.Map.GetRenderMgr().renderDone.disconnect(self.MapWindow3D._onUpdateOverlays) 495 self.MapWindow3D.ClearTextures() 496 497 self.MapWindow.UpdateMap() 498 self._mgr.Update() 499 self.GetMapToolbar().SelectDefault() 500 501 def AddToolbar(self, name, fixed=False): 502 """Add defined toolbar to the window 503 504 Currently recognized toolbars are: 505 - 'map' - basic map toolbar 506 - 'vdigit' - vector digitizer 507 508 :param name: toolbar to add 509 :param fixed: fixed toolbar 510 """ 511 # default toolbar 512 if name == "map": 513 if 'map' not in self.toolbars: 514 self.toolbars['map'] = MapToolbar( 515 self, toolSwitcher=self._toolSwitcher) 516 517 self._mgr.AddPane(self.toolbars['map'], 518 wx.aui.AuiPaneInfo(). 519 Name("maptoolbar").Caption(_("Map Toolbar")). 520 ToolbarPane().Top().Name('mapToolbar'). 521 LeftDockable(False).RightDockable(False). 522 BottomDockable(False).TopDockable(True). 523 CloseButton(False).Layer(2). 524 BestSize((self.toolbars['map'].GetBestSize()))) 525 526 # vector digitizer 527 elif name == "vdigit": 528 self.toolbars['map'].combo.SetValue(_("Vector digitizer")) 529 self._addToolbarVDigit() 530 531 if fixed: 532 self.toolbars['map'].combo.Disable() 533 534 self._mgr.Update() 535 536 def RemoveToolbar(self, name, destroy=False): 537 """Removes defined toolbar from the window 538 539 :param name toolbar to remove 540 :param destroy True to destroy otherwise toolbar is only hidden 541 """ 542 self._mgr.DetachPane(self.toolbars[name]) 543 if destroy: 544 self._toolSwitcher.RemoveToolbarFromGroup( 545 'mouseUse', self.toolbars[name]) 546 self.toolbars[name].Destroy() 547 self.toolbars.pop(name) 548 else: 549 self.toolbars[name].Hide() 550 551 if name == 'vdigit': 552 self._mgr.GetPane('vdigit').Hide() 553 self._mgr.GetPane('2d').Show() 554 self._switchMapWindow(self.MapWindow2D) 555 556 self.toolbars['map'].Enable2D(True) 557 558 self._mgr.Update() 559 560 def IsPaneShown(self, name): 561 """Check if pane (toolbar, mapWindow ...) of given name is currently shown""" 562 if self._mgr.GetPane(name).IsOk(): 563 return self._mgr.GetPane(name).IsShown() 564 return False 565 566 def RemoveQueryLayer(self): 567 """Removes temporary map layers (queries)""" 568 qlayer = self.GetMap().GetListOfLayers(name=globalvar.QUERYLAYER) 569 for layer in qlayer: 570 self.GetMap().DeleteLayer(layer) 571 572 def OnRender(self, event): 573 """Re-render map composition (each map layer) 574 """ 575 self.RemoveQueryLayer() 576 577 # deselect features in vdigit 578 if self.GetToolbar('vdigit'): 579 if self.MapWindow.digit: 580 self.MapWindow.digit.GetDisplay().SetSelected([]) 581 self.MapWindow.UpdateMap(render=True, renderVector=True) 582 else: 583 self.MapWindow.UpdateMap(render=True) 584 585 # reset dialog with selected features 586 if self.dialogs['vselect']: 587 self.dialogs['vselect'].Reset() 588 589 # update statusbar 590 self.StatusbarUpdate() 591 592 def OnPointer(self, event): 593 """Pointer button clicked 594 """ 595 self.MapWindow.SetModePointer() 596 597 if self.GetToolbar('vdigit'): 598 self.toolbars['vdigit'].action['id'] = -1 599 self.toolbars['vdigit'].action['desc'] = '' 600 601 def OnSelect(self, event): 602 """Vector feature selection button clicked 603 """ 604 layerList = self._giface.GetLayerList() 605 layerSelected = layerList.GetSelectedLayer() 606 if not self.dialogs['vselect']: 607 if layerSelected is None: 608 GMessage(_("No map layer selected. Operation canceled.")) 609 return 610 611 self.dialogs['vselect'] = VectorSelectBase( 612 self.parent, self._giface) 613 self.dialogs['vselect'].CreateDialog(createButton=True) 614 self.dialogs['vselect'].onCloseDialog.connect( 615 self._onCloseVectorSelectDialog) 616 617 def _onCloseVectorSelectDialog(self): 618 self.dialogs['vselect'] = None 619 620 def OnRotate(self, event): 621 """Rotate 3D view 622 """ 623 self.MapWindow.mouse['use'] = "rotate" 624 625 # change the cursor 626 self.MapWindow.SetNamedCursor('hand') 627 628 def OnFlyThrough(self, event): 629 """Fly-through mode 630 """ 631 self.MapWindow.mouse['use'] = "fly" 632 633 # change the cursor 634 self.MapWindow.SetNamedCursor('hand') 635 self.MapWindow.SetFocus() 636 637 def SaveToFile(self, event): 638 """Save map to image 639 """ 640 filetype, ltype = self._prepareSaveToFile() 641 if not ltype: 642 return 643 644 # get size 645 dlg = ImageSizeDialog(self) 646 dlg.CentreOnParent() 647 if dlg.ShowModal() != wx.ID_OK: 648 dlg.Destroy() 649 return 650 width, height = dlg.GetValues() 651 dlg.Destroy() 652 653 # get filename 654 dlg = wx.FileDialog(parent=self, 655 message=_("Choose a file name to save the image " 656 "(no need to add extension)"), 657 wildcard=filetype, 658 style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) 659 660 if dlg.ShowModal() == wx.ID_OK: 661 path = dlg.GetPath() 662 if not path: 663 dlg.Destroy() 664 return 665 666 base, ext = os.path.splitext(path) 667 fileType = ltype[dlg.GetFilterIndex()]['type'] 668 extType = ltype[dlg.GetFilterIndex()]['ext'] 669 if ext != extType: 670 path = base + '.' + extType 671 672 self.MapWindow.SaveToFile(path, fileType, 673 width, height) 674 675 dlg.Destroy() 676 677 def DOutFile(self, command, callback=None): 678 """Saves map to image by running d.out.file from gui or d.mon. 679 Command is expected to be validated by parser. 680 """ 681 filetype, ltype = self._prepareSaveToFile() 682 if not ltype: 683 return 684 width, height = self.MapWindow.GetClientSize() 685 for param in command[1:]: 686 try: 687 p, val = param.split('=') 688 except ValueError: 689 # --overwrite 690 continue 691 if p == 'format': # must be there 692 if self.IsPaneShown('3d'): 693 extType = 'ppm' 694 else: 695 extType = val 696 if p == 'output': # must be there 697 name = val 698 elif p == 'size': 699 width, height = val.split(',') 700 701 base, ext = os.path.splitext(name) 702 if not ext: 703 name = base + '.' + extType 704 elif ext[1:] != extType: 705 extType = ext[1:] 706 707 if self.IsPaneShown('3d'): 708 bitmapType = 'ppm' 709 else: 710 bitmapType = wx.BITMAP_TYPE_PNG # default type 711 for each in ltype: 712 if each['ext'] == extType: 713 bitmapType = each['type'] 714 break 715 self.MapWindow.SaveToFile(name, bitmapType, int(width), int(height), callback) 716 717 def DOutFileOptData(self, dcmd, layer, params, propwin): 718 """Dummy function which is called when d.out.file is called 719 and returns parsed and validated command which is then passed 720 to DOutFile method.""" 721 if not dcmd: 722 return 723 724 self.DOutFile(dcmd) 725 726 def DToRast(self, command): 727 """Saves currently loaded composition of layers as a raster map. 728 """ 729 def _DToRastDone(): 730 # import back as red, green, blue rasters 731 returncode, messages = RunCommand( 732 'r.in.gdal', flags='o', input=pngFile, output=tmpName, quiet=True, 733 overwrite=overwrite, getErrorMsg=True) 734 if not returncode == 0: 735 self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages) 736 return 737 # set region for composite 738 grass.use_temp_region() 739 returncode, messages = RunCommand('g.region', raster=tmpName + '.red', 740 quiet=True, getErrorMsg=True) 741 if not returncode == 0: 742 grass.del_temp_region() 743 self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages) 744 return 745 # composite 746 returncode, messages = RunCommand( 747 'r.composite', red=tmpName + '.red', green=tmpName + '.green', 748 blue=tmpName + '.blue', output=outputRaster, quiet=True, 749 overwrite=overwrite, getErrorMsg=True) 750 grass.del_temp_region() 751 RunCommand( 752 'g.remove', 753 type='raster', 754 flags='f', 755 quiet=True, 756 name=[tmpName + '.red', tmpName + '.green', tmpName + '.blue']) 757 if not returncode == 0: 758 self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages) 759 grass.try_remove(pngFile) 760 return 761 762 # alignExtent changes only region variable 763 oldRegion = self.GetMap().GetCurrentRegion().copy() 764 self.GetMap().AlignExtentFromDisplay() 765 region = self.GetMap().GetCurrentRegion().copy() 766 self.GetMap().region.update(oldRegion) 767 RunCommand('r.region', map=outputRaster, n=region['n'], s=region['s'], 768 e=region['e'], w=region['w'], quiet=True) 769 grass.try_remove(pngFile) 770 771 772 if self.IsPaneShown('3d'): 773 self._giface.WriteError( 774 _('d.to.rast can be used only in 2D mode.')) 775 return 776 outputRaster = None 777 overwrite = False 778 for param in command[1:]: 779 try: 780 p, val = param.split('=') 781 if p == 'output': 782 outputRaster = val 783 except ValueError: 784 if param.startswith('--overwrite'): 785 overwrite = True 786 787 if not outputRaster: 788 return 789 # output file as PNG 790 tmpName = 'd_to_rast_tmp' 791 pngFile = grass.tempfile(create=False) + '.png' 792 dOutFileCmd = ['d.out.file', 'output=' + pngFile, 'format=png'] 793 self.DOutFile(dOutFileCmd, callback=_DToRastDone) 794 795 796 797 def DToRastOptData(self, dcmd, layer, params, propwin): 798 """Dummy function which is called when d.to.rast is called 799 and returns parsed and validated command which is then passed 800 to DToRast method.""" 801 if not dcmd: 802 return 803 804 self.DToRast(dcmd) 805 806 def _prepareSaveToFile(self): 807 """Get wildcards and format extensions.""" 808 if self.IsPaneShown('3d'): 809 filetype = "TIF file (*.tif)|*.tif|PPM file (*.ppm)|*.ppm" 810 ltype = [{'ext': 'tif', 'type': 'tif'}, 811 {'ext': 'ppm', 'type': 'ppm'}] 812 else: 813 img = self.MapWindow.img 814 if not img: 815 GMessage( 816 parent=self, 817 message=_( 818 "Nothing to render (empty map). Operation canceled.")) 819 return None, None 820 filetype, ltype = GetImageHandlers(img) 821 return filetype, ltype 822 823 def PrintMenu(self, event): 824 """ 825 Print options and output menu for map display 826 """ 827 printmenu = Menu() 828 # Add items to the menu 829 setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup')) 830 printmenu.AppendItem(setup) 831 self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup) 832 833 preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview')) 834 printmenu.AppendItem(preview) 835 self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview) 836 837 doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display')) 838 printmenu.AppendItem(doprint) 839 self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint) 840 841 # Popup the menu. If an item is selected then its handler 842 # will be called before PopupMenu returns. 843 self.PopupMenu(printmenu) 844 printmenu.Destroy() 845 846 def CleanUp(self): 847 """Clean up before closing map display. 848 End digitizer/nviz.""" 849 Debug.msg(2, "MapFrame.CleanUp()") 850 self.Map.Clean() 851 # close edited map and 3D tools properly 852 if self.GetToolbar('vdigit'): 853 maplayer = self.toolbars['vdigit'].GetLayer() 854 if maplayer: 855 self.toolbars['vdigit'].OnExit() 856 if self.IsPaneShown('3d'): 857 self.RemoveNviz() 858 if hasattr(self, 'rdigit') and self.rdigit: 859 self.rdigit.CleanUp() 860 if self.dialogs['vnet']: 861 self.closingVNETDialog.emit() 862 self._mgr.UnInit() 863 864 def OnCloseWindow(self, event, askIfSaveWorkspace=True): 865 """Window closed. 866 Also close associated layer tree page 867 """ 868 Debug.msg(2, "MapFrame.OnCloseWindow()") 869 if self._layerManager: 870 pgnum = self.layerbook.GetPageIndex(self.page) 871 name = self.layerbook.GetPageText(pgnum) 872 caption = _("Close Map Display {}").format(name) 873 if not askIfSaveWorkspace or \ 874 (askIfSaveWorkspace and self._layerManager.CanClosePage(caption)): 875 self.CleanUp() 876 if pgnum > -1: 877 self.closingDisplay.emit(page_index=pgnum) 878 # Destroy is called when notebook page is deleted 879 else: 880 self.CleanUp() 881 self.Destroy() 882 883 def Query(self, x, y): 884 """Query selected layers. 885 886 :param x,y: coordinates 887 """ 888 if self._vectQueryLayers or self._rastQueryLayers: 889 rast = self._rastQueryLayers 890 vect = self._vectQueryLayers 891 else: 892 layers = self._giface.GetLayerList().GetSelectedLayers(checkedOnly=False) 893 rast = [] 894 vect = [] 895 for layer in layers: 896 if layer.type == 'command': 897 continue 898 name, found = GetLayerNameFromCmd(layer.cmd) 899 if not found: 900 continue 901 ltype = layer.maplayer.GetType() 902 if ltype == 'raster': 903 rast.append(name) 904 elif ltype in ('rgb', 'his'): 905 for iname in name.split('\n'): 906 rast.append(iname) 907 elif ltype in ('vector', 'thememap', 'themechart'): 908 vect.append(name) 909 if vect: 910 # check for vector maps open to be edited 911 digitToolbar = self.GetToolbar('vdigit') 912 if digitToolbar: 913 lmap = digitToolbar.GetLayer().GetName() 914 for name in vect: 915 if lmap == name: 916 self._giface.WriteWarning( 917 _("Vector map <%s> " "opened for editing - skipped.") % lmap) 918 vect.remove(name) 919 920 if not (rast + vect): 921 GMessage( 922 parent=self, 923 message=_( 924 'No raster or vector map layer selected for querying.')) 925 return 926 927 # set query snap distance for v.what at map unit equivalent of 10 928 # pixels 929 qdist = 10.0 * ( 930 (self.Map.region['e'] - self.Map.region['w']) / self.Map.width) 931 932 # TODO: replace returning None by exception or so 933 try: 934 east, north = self.MapWindow.Pixel2Cell((x, y)) 935 except TypeError: 936 return 937 938 if not self.IsPaneShown('3d'): 939 self.QueryMap(east, north, qdist, rast, vect) 940 else: 941 if rast: 942 self.MapWindow.QuerySurface(x, y) 943 if vect: 944 self.QueryMap(east, north, qdist, rast=[], vect=vect) 945 946 def SetQueryLayersAndActivate(self, ltype, maps): 947 """Activate query mode and set layers to query. 948 This method is used for querying in d.mon using d.what.rast/vect""" 949 self.toolbars['map'].SelectTool(self.toolbars['map'].query) 950 if ltype == 'vector': 951 self._vectQueryLayers = maps 952 elif ltype == 'raster': 953 self._rastQueryLayers = maps 954 955 def QueryMap(self, east, north, qdist, rast, vect): 956 """Query raster or vector map layers by r/v.what 957 958 :param east,north: coordinates 959 :param qdist: query distance 960 :param rast: raster map names 961 :param vect: vector map names 962 """ 963 Debug.msg(1, "QueryMap(): raster=%s vector=%s" % (','.join(rast), 964 ','.join(vect))) 965 if self._highlighter_layer is None: 966 self._highlighter_layer = VectorSelectHighlighter( 967 mapdisp=self._giface.GetMapDisplay(), giface=self._giface) 968 969 # use display region settings instead of computation region settings 970 self.tmpreg = os.getenv("GRASS_REGION") 971 os.environ["GRASS_REGION"] = self.Map.SetRegion(windres=False) 972 973 rastQuery = [] 974 vectQuery = [] 975 if rast: 976 rastQuery = grass.raster_what(map=rast, coord=(east, north), 977 localized=True) 978 if vect: 979 encoding = UserSettings.Get( 980 group='atm', key='encoding', subkey='value') 981 try: 982 vectQuery = grass.vector_what( 983 map=vect, coord=(east, north), 984 distance=qdist, encoding=encoding, multiple=True) 985 except grass.ScriptError: 986 GError( 987 parent=self, message=_( 988 "Failed to query vector map(s) <{maps}>. " 989 "Check database settings and topology.").format( 990 maps=','.join(vect))) 991 self._QueryMapDone() 992 993 self._highlighter_layer.Clear() 994 if vectQuery and 'Category' in vectQuery[0]: 995 self._queryHighlight(vectQuery) 996 997 result = rastQuery + vectQuery 998 result = PrepareQueryResults(coordinates=(east, north), result=result) 999 if self.dialogs['query']: 1000 self.dialogs['query'].Raise() 1001 self.dialogs['query'].SetData(result) 1002 else: 1003 self.dialogs['query'] = QueryDialog(parent=self, data=result) 1004 self.dialogs['query'].Bind(wx.EVT_CLOSE, self._oncloseQueryDialog) 1005 self.dialogs['query'].redirectOutput.connect( 1006 self._onRedirectQueryOutput) 1007 self.dialogs['query'].Show() 1008 1009 def _oncloseQueryDialog(self, event): 1010 self.dialogs['query'] = None 1011 self._vectQueryLayers = [] 1012 self._rastQueryLayers = [] 1013 self._highlighter_layer.Clear() 1014 self._highlighter_layer = None 1015 event.Skip() 1016 1017 def _onRedirectQueryOutput(self, output, style='log'): 1018 """Writes query output into console""" 1019 if style == 'log': 1020 self._giface.WriteLog( 1021 output, notification=Notification.MAKE_VISIBLE) 1022 elif style == 'cmd': 1023 self._giface.WriteCmdLog(output) 1024 1025 def _queryHighlight(self, vectQuery): 1026 """Highlight category from query.""" 1027 if len(vectQuery) > 0: 1028 self._highlighter_layer.SetLayer(vectQuery[0]['Layer']) 1029 self._highlighter_layer.SetMap( 1030 vectQuery[0]['Map'] + '@' + vectQuery[0]['Mapset'] 1031 ) 1032 tmp = list() 1033 for i in vectQuery: 1034 tmp.append(i['Category']) 1035 1036 self._highlighter_layer.SetCats(tmp) 1037 self._highlighter_layer.DrawSelected() 1038 1039 def _QueryMapDone(self): 1040 """Restore settings after querying (restore GRASS_REGION) 1041 """ 1042 if hasattr(self, "tmpreg"): 1043 if self.tmpreg: 1044 os.environ["GRASS_REGION"] = self.tmpreg 1045 elif 'GRASS_REGION' in os.environ: 1046 del os.environ["GRASS_REGION"] 1047 elif 'GRASS_REGION' in os.environ: 1048 del os.environ["GRASS_REGION"] 1049 1050 if hasattr(self, "tmpreg"): 1051 del self.tmpreg 1052 1053 def OnQuery(self, event): 1054 """Query tools menu""" 1055 self.MapWindow.mouse['use'] = "query" 1056 self.MapWindow.mouse['box'] = "point" 1057 self.MapWindow.zoomtype = 0 1058 1059 # change the cursor 1060 self.MapWindow.SetNamedCursor('cross') 1061 1062 def AddTmpVectorMapLayer(self, name, cats, useId=False, addLayer=True): 1063 """Add temporal vector map layer to map composition 1064 1065 :param name: name of map layer 1066 :param useId: use feature id instead of category 1067 """ 1068 # color settings from ATM 1069 color = UserSettings.Get(group='atm', key='highlight', subkey='color') 1070 colorStr = str(color[0]) + ":" + \ 1071 str(color[1]) + ":" + \ 1072 str(color[2]) 1073 1074 # icon used in vector display and its size 1075 icon = '' 1076 size = 0 1077 # here we know that there is one selected layer and it is vector 1078 layerSelected = self._giface.GetLayerList().GetSelectedLayer() 1079 if not layerSelected: 1080 return None 1081 1082 vparam = layerSelected.cmd 1083 for p in vparam: 1084 if '=' in p: 1085 parg, pval = p.split('=', 1) 1086 if parg == 'icon': 1087 icon = pval 1088 elif parg == 'size': 1089 size = float(pval) 1090 1091 pattern = [ 1092 "d.vect", 1093 "map=%s" % name, 1094 "color=%s" % colorStr, 1095 "fill_color=%s" % colorStr, 1096 "width=%d" % UserSettings.Get( 1097 group='atm', 1098 key='highlight', 1099 subkey='width') 1100 ] 1101 if icon != '': 1102 pattern.append('icon=%s' % icon) 1103 if size > 0: 1104 pattern.append('size=%i' % size) 1105 1106 if useId: 1107 cmd = pattern 1108 cmd.append('-i') 1109 cmd.append('cats=%s' % str(cats)) 1110 else: 1111 cmd = [] 1112 for layer in cats.keys(): 1113 cmd.append(copy.copy(pattern)) 1114 lcats = cats[layer] 1115 cmd[-1].append("layer=%d" % layer) 1116 cmd[-1].append("cats=%s" % ListOfCatsToRange(lcats)) 1117 1118 if addLayer: 1119 args = {} 1120 if useId: 1121 args['ltype'] = 'vector' 1122 else: 1123 args['ltype'] = 'command' 1124 1125 return self.Map.AddLayer(name=globalvar.QUERYLAYER, command=cmd, 1126 active=True, hidden=True, opacity=1.0, 1127 render=True, **args) 1128 else: 1129 return cmd 1130 1131 def OnMeasureDistance(self, event): 1132 self._onMeasure(MeasureDistanceController) 1133 1134 def OnMeasureArea(self, event): 1135 self._onMeasure(MeasureAreaController) 1136 1137 def _onMeasure(self, controller): 1138 """Starts measurement mode. 1139 1140 :param controller: measurement class (MeasureDistanceController, MeasureAreaController) 1141 """ 1142 self.measureController = controller( 1143 self._giface, mapWindow=self.GetMapWindow()) 1144 # assure that the mode is ended and lines are cleared whenever other 1145 # tool is selected 1146 self._toolSwitcher.toggleToolChanged.connect( 1147 lambda: self.measureController.Stop()) 1148 self.measureController.Start() 1149 1150 def OnProfile(self, event): 1151 """Launch profile tool 1152 """ 1153 rasters = [] 1154 layers = self._giface.GetLayerList().GetSelectedLayers() 1155 for layer in layers: 1156 if layer.type == 'raster': 1157 rasters.append(layer.maplayer.name) 1158 self.Profile(rasters=rasters) 1159 1160 def Profile(self, rasters=None): 1161 """Launch profile tool""" 1162 from wxplot.profile import ProfileFrame 1163 1164 self.profileController = ProfileController( 1165 self._giface, mapWindow=self.GetMapWindow()) 1166 win = ProfileFrame(parent=self, giface=self._giface, rasterList=rasters, 1167 units=self.Map.projinfo['units'], 1168 controller=self.profileController) 1169 win.Show() 1170 # Open raster select dialog to make sure that a raster (and 1171 # the desired raster) is selected to be profiled 1172 win.OnSelectRaster(None) 1173 1174 def OnHistogramPyPlot(self, event): 1175 """Init PyPlot histogram display canvas and tools 1176 """ 1177 raster = [] 1178 1179 for layer in self._giface.GetLayerList().GetSelectedLayers(): 1180 if layer.maplayer.GetType() == 'raster': 1181 raster.append(layer.maplayer.GetName()) 1182 1183 from wxplot.histogram import HistogramPlotFrame 1184 win = HistogramPlotFrame(parent=self, giface=self._giface, 1185 rasterList=raster) 1186 win.CentreOnParent() 1187 win.Show() 1188 1189 def OnScatterplot(self, event): 1190 """Init PyPlot scatterplot display canvas and tools 1191 """ 1192 raster = [] 1193 1194 for layer in self._giface.GetLayerList().GetSelectedLayers(): 1195 if layer.maplayer.GetType() == 'raster': 1196 raster.append(layer.maplayer.GetName()) 1197 1198 from wxplot.scatter import ScatterFrame 1199 win = ScatterFrame(parent=self, giface=self._giface, rasterList=raster) 1200 1201 win.CentreOnParent() 1202 win.Show() 1203 # Open raster select dialog to make sure that at least 2 rasters (and the desired rasters) 1204 # are selected to be plotted 1205 win.OnSelectRaster(None) 1206 1207 def OnHistogram(self, event): 1208 """Init histogram display canvas and tools 1209 """ 1210 from modules.histogram import HistogramFrame 1211 win = HistogramFrame(self, giface=self._giface) 1212 1213 win.CentreOnParent() 1214 win.Show() 1215 win.Refresh() 1216 win.Update() 1217 1218 def _activateOverlay(self, overlayId): 1219 """Launch decoration dialog according to overlay id. 1220 1221 :param overlayId: id of overlay 1222 """ 1223 if overlayId in self.decorations: 1224 dlg = self.decorations[overlayId].dialog 1225 if dlg.IsShown(): 1226 dlg.SetFocus() 1227 dlg.Raise() 1228 else: 1229 dlg.Show() 1230 1231 def RemoveOverlay(self, overlayId): 1232 """Hide overlay. 1233 1234 :param overlayId: id of overlay 1235 """ 1236 del self._decorationWindows[self.decorations[overlayId].dialog] 1237 self.decorations[overlayId].Remove() 1238 del self.decorations[overlayId] 1239 1240 def AddBarscale(self, cmd=None): 1241 """Handler for scale bar map decoration menu selection.""" 1242 if self.IsPaneShown('3d'): 1243 self.MapWindow3D.SetDrawScalebar((70, 70)) 1244 return 1245 1246 if cmd: 1247 show = False 1248 else: 1249 show = True 1250 cmd = ['d.barscale'] 1251 1252 # Decoration overlay control dialog 1253 GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand( 1254 cmd, completed=(self.GetOptData, None, None)) 1255 1256 self.MapWindow.mouse['use'] = 'pointer' 1257 1258 def AddLegendRast(self, cmd=None): 1259 """Handler for legend raster map decoration menu selection.""" 1260 1261 if cmd: 1262 show = False 1263 else: 1264 show = True 1265 cmd = ['d.legend'] 1266 layers = self._giface.GetLayerList().GetSelectedLayers() 1267 for layer in layers: 1268 if layer.type == 'raster': 1269 cmd.append('raster={rast}'.format(rast=layer.maplayer.name)) 1270 break 1271 1272 GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand( 1273 cmd, completed=(self.GetOptData, None, None)) 1274 1275 self.MapWindow.mouse['use'] = 'pointer' 1276 1277 def AddLegendVect(self, cmd=None, showDialog=None): 1278 """Handler for legend vector map decoration menu selection.""" 1279 1280 if cmd: 1281 show = False 1282 else: 1283 show = True 1284 cmd = ['d.legend.vect'] 1285 1286 GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand( 1287 cmd, completed=(self.GetOptData, None, None)) 1288 1289 self.MapWindow.mouse['use'] = 'pointer' 1290 1291 def AddArrow(self, cmd=None): 1292 """Handler for north arrow menu selection.""" 1293 if self.IsPaneShown('3d'): 1294 # here was opening of appearance page of nviz notebook 1295 # but now moved to MapWindow3D where are other problematic nviz 1296 # calls 1297 self.MapWindow3D.SetDrawArrow((70, 70)) 1298 return 1299 1300 if cmd: 1301 show = False 1302 else: 1303 show = True 1304 cmd = ['d.northarrow'] 1305 1306 # Decoration overlay control dialog 1307 GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand( 1308 cmd, completed=(self.GetOptData, None, None)) 1309 1310 self.MapWindow.mouse['use'] = 'pointer' 1311 1312 def AddDtext(self, cmd=None): 1313 """Handler for d.text menu selection.""" 1314 if cmd: 1315 show = False 1316 else: 1317 show = True 1318 cmd = ['d.text'] 1319 1320 # Decoration overlay control dialog 1321 GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand( 1322 cmd, completed=(self.GetOptData, None, None)) 1323 1324 self.MapWindow.mouse['use'] = 'pointer' 1325 1326 def GetOptData(self, dcmd, layer, params, propwin): 1327 """Called after options are set through module dialog. 1328 1329 :param dcmd: resulting command 1330 :param layer: not used 1331 :param params: module parameters (not used) 1332 :param propwin: dialog window 1333 """ 1334 1335 if not dcmd: 1336 return 1337 if propwin in self._decorationWindows: 1338 overlay = self._decorationWindows[propwin] 1339 else: 1340 cmd = dcmd[0] 1341 if cmd == 'd.northarrow': 1342 overlay = ArrowController(self.Map, self._giface) 1343 elif cmd == 'd.barscale': 1344 overlay = BarscaleController(self.Map, self._giface) 1345 elif cmd == 'd.legend': 1346 overlay = LegendController(self.Map, self._giface) 1347 elif cmd == 'd.legend.vect': 1348 overlay = LegendVectController(self.Map, self._giface) 1349 elif cmd == 'd.text': 1350 overlay = DtextController(self.Map, self._giface) 1351 1352 self.decorations[overlay.id] = overlay 1353 overlay.overlayChanged.connect(lambda: self.MapWindow2D.UpdateMap( 1354 render=False, renderVector=False)) 1355 if self.IsPaneShown('3d'): 1356 overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays) 1357 1358 overlay.dialog = propwin 1359 self._decorationWindows[propwin] = overlay 1360 1361 overlay.cmd = dcmd 1362 overlay.Show() 1363 1364 def OnZoomToMap(self, event): 1365 """Set display extents to match selected raster (including 1366 NULLs) or vector map. 1367 """ 1368 Debug.msg(3, "MapFrame.OnZoomToMap()") 1369 layers = None 1370 if self.IsStandalone(): 1371 layers = self.MapWindow.GetMap().GetListOfLayers(active=False) 1372 1373 self.MapWindow.ZoomToMap(layers=layers) 1374 1375 def OnZoomToRaster(self, event): 1376 """Set display extents to match selected raster map (ignore NULLs) 1377 """ 1378 self.MapWindow.ZoomToMap(ignoreNulls=True) 1379 1380 def OnZoomToSaved(self, event): 1381 """Set display geometry to match extents in 1382 saved region file 1383 """ 1384 self.MapWindow.SetRegion(zoomOnly=True) 1385 1386 def OnSetDisplayToWind(self, event): 1387 """Set computational region (WIND file) to match display 1388 extents 1389 """ 1390 self.MapWindow.DisplayToWind() 1391 1392 def OnSetWindToRegion(self, event): 1393 """Set computational region (WIND file) from named region 1394 file 1395 """ 1396 self.MapWindow.SetRegion(zoomOnly=False) 1397 1398 def OnSetExtentToWind(self, event): 1399 """Set compulational region extent interactively""" 1400 self.MapWindow.SetModeDrawRegion() 1401 1402 def OnSaveDisplayRegion(self, event): 1403 """Save display extents to named region file. 1404 """ 1405 self.MapWindow.SaveRegion(display=True) 1406 1407 def OnSaveWindRegion(self, event): 1408 """Save computational region to named region file. 1409 """ 1410 self.MapWindow.SaveRegion(display=False) 1411 1412 def OnZoomMenu(self, event): 1413 """Popup Zoom menu 1414 """ 1415 zoommenu = Menu() 1416 1417 for label, handler in ( 1418 (_('Zoom to default region'), 1419 self.OnZoomToDefault), 1420 (_('Zoom to saved region'), 1421 self.OnZoomToSaved), 1422 (None, None), 1423 (_('Set computational region extent from display'), 1424 self.OnSetDisplayToWind), 1425 (_('Set computational region extent interactively'), 1426 self.OnSetExtentToWind), 1427 (_('Set computational region from named region'), 1428 self.OnSetWindToRegion), 1429 (None, None), 1430 (_('Save display geometry to named region'), 1431 self.OnSaveDisplayRegion), 1432 (_('Save computational region to named region'), 1433 self.OnSaveWindRegion)): 1434 if label: 1435 mid = wx.MenuItem(zoommenu, wx.ID_ANY, label) 1436 zoommenu.AppendItem(mid) 1437 self.Bind(wx.EVT_MENU, handler, mid) 1438 else: 1439 zoommenu.AppendSeparator() 1440 1441 # Popup the menu. If an item is selected then its handler will 1442 # be called before PopupMenu returns. 1443 self.PopupMenu(zoommenu) 1444 zoommenu.Destroy() 1445 1446 def SetProperties(self, render=False, mode=0, showCompExtent=False, 1447 constrainRes=False, projection=False, alignExtent=True): 1448 """Set properies of map display window""" 1449 self.mapWindowProperties.autoRender = render 1450 if self.statusbarManager: 1451 self.statusbarManager.SetMode(mode) 1452 self.StatusbarUpdate() 1453 self.SetProperty('projection', projection) 1454 self.mapWindowProperties.showRegion = showCompExtent 1455 self.mapWindowProperties.alignExtent = alignExtent 1456 self.mapWindowProperties.resolution = constrainRes 1457 1458 def IsStandalone(self): 1459 """Check if Map display is standalone 1460 1461 .. deprecated:: 7.0 1462 """ 1463 # TODO: once it is removed from 2 places in vdigit it can be deleted 1464 # here and also in base class and other classes in the tree (hopefully) 1465 # and one place here still uses IsStandalone 1466 Debug.msg(1, "MapFrame.IsStandalone(): Method IsStandalone is" 1467 "depreciated, use some general approach instead such as" 1468 " Signals or giface") 1469 if self._layerManager: 1470 return False 1471 1472 return True 1473 1474 def GetLayerManager(self): 1475 """Get reference to Layer Manager 1476 1477 :return: window reference 1478 :return: None (if standalone) 1479 1480 .. deprecated:: 7.0 1481 """ 1482 Debug.msg(1, "MapFrame.GetLayerManager(): Method GetLayerManager is" 1483 "depreciated, use some general approach instead such as" 1484 " Signals or giface") 1485 return self._layerManager 1486 1487 def GetMapToolbar(self): 1488 """Returns toolbar with zooming tools""" 1489 return self.toolbars['map'] if 'map' in self.toolbars else None 1490 1491 def GetToolbarNames(self): 1492 """Return toolbar names""" 1493 return self.toolbars.keys() 1494 1495 def GetDialog(self, name): 1496 """Get selected dialog if exist""" 1497 return self.dialogs.get(name, None) 1498 1499 def OnVNet(self, event): 1500 """Dialog for v.net* modules 1501 """ 1502 if self.dialogs['vnet']: 1503 self.dialogs['vnet'].Raise() 1504 return 1505 1506 from vnet.dialogs import VNETDialog 1507 self.dialogs['vnet'] = VNETDialog(parent=self, giface=self._giface) 1508 self.closingVNETDialog.connect(self.dialogs['vnet'].OnCloseDialog) 1509 self.dialogs['vnet'].CenterOnScreen() 1510 self.dialogs['vnet'].Show() 1511 1512 def ResetPointer(self): 1513 """Sets pointer mode. 1514 1515 Sets pointer and toggles it (e.g. after unregistration of mouse 1516 handler). 1517 """ 1518 self.GetMapToolbar().SelectDefault() 1519 1520 def _switchMapWindow(self, map_win): 1521 """Notifies activated and disactivated map_wins.""" 1522 self.MapWindow.DisactivateWin() 1523 map_win.ActivateWin() 1524 1525 self.MapWindow = map_win 1526 1527 def AddRDigit(self): 1528 """Adds raster digitizer: creates toolbar and digitizer controller, 1529 binds events and signals.""" 1530 from rdigit.controller import RDigitController, EVT_UPDATE_PROGRESS 1531 from rdigit.toolbars import RDigitToolbar 1532 1533 self.rdigit = RDigitController(self._giface, 1534 mapWindow=self.GetMapWindow()) 1535 self.toolbars['rdigit'] = RDigitToolbar( 1536 parent=self, giface=self._giface, controller=self.rdigit, 1537 toolSwitcher=self._toolSwitcher) 1538 # connect signals 1539 self.rdigit.newRasterCreated.connect( 1540 self.toolbars['rdigit'].NewRasterAdded) 1541 self.rdigit.newRasterCreated.connect( 1542 lambda name: self._giface.mapCreated.emit( 1543 name=name, ltype='raster')) 1544 self.rdigit.newFeatureCreated.connect( 1545 self.toolbars['rdigit'].UpdateCellValues) 1546 self.rdigit.uploadMapCategories.connect( 1547 self.toolbars['rdigit'].UpdateCellValues) 1548 self.rdigit.showNotification.connect( 1549 lambda text: self.SetStatusText(text, 0)) 1550 self.rdigit.quitDigitizer.connect(self.QuitRDigit) 1551 self.rdigit.Bind( 1552 EVT_UPDATE_PROGRESS, 1553 lambda evt: self.statusbarManager.SetProgress( 1554 evt.range, 1555 evt.value, 1556 evt.text)) 1557 rasters = self.GetMap().GetListOfLayers( 1558 ltype='raster', mapset=grass.gisenv()['MAPSET']) 1559 self.toolbars['rdigit'].UpdateRasterLayers(rasters) 1560 self.toolbars['rdigit'].SelectDefault() 1561 1562 self.GetMap().layerAdded.connect(self._updateRDigitLayers) 1563 self.GetMap().layerRemoved.connect(self._updateRDigitLayers) 1564 self.GetMap().layerChanged.connect(self._updateRDigitLayers) 1565 self._mgr.AddPane(self.toolbars['rdigit'], 1566 wx.aui.AuiPaneInfo(). 1567 Name("rdigit toolbar").Caption(_("Raster Digitizer Toolbar")). 1568 ToolbarPane().Top().Row(1). 1569 LeftDockable(False).RightDockable(False). 1570 BottomDockable(False).TopDockable(True).Floatable(). 1571 CloseButton(False).Layer(2).DestroyOnClose(). 1572 BestSize((self.toolbars['rdigit'].GetBestSize()))) 1573 self._mgr.Update() 1574 1575 self.rdigit.Start() 1576 1577 def _updateRDigitLayers(self, layer): 1578 mapset = grass.gisenv()['MAPSET'] 1579 self.toolbars['rdigit'].UpdateRasterLayers( 1580 rasters=self.GetMap().GetListOfLayers( 1581 ltype='raster', mapset=mapset)) 1582 1583 def QuitRDigit(self): 1584 """Calls digitizer cleanup, removes digitizer object and disconnects 1585 signals from Map.""" 1586 self.rdigit.CleanUp() 1587 # disconnect updating layers 1588 self.GetMap().layerAdded.disconnect(self._updateRDigitLayers) 1589 self.GetMap().layerRemoved.disconnect(self._updateRDigitLayers) 1590 self.GetMap().layerChanged.disconnect(self._updateRDigitLayers) 1591 self._toolSwitcher.toggleToolChanged.disconnect(self.toolbars['rdigit'].CheckSelectedTool) 1592 1593 self.RemoveToolbar('rdigit', destroy=True) 1594 self.rdigit = None 1595 1596 def QuitVDigit(self): 1597 """Quit VDigit""" 1598 if not self.IsStandalone(): 1599 # disable the toolbar 1600 self.RemoveToolbar("vdigit", destroy=True) 1601 else: 1602 self.Close() 1603