4
@brief Map display with toolbar for various display management
5
functions, and additional toolbars (vector digitizer, 3d view).
7
Can be used either from Layer Manager or as d.mon backend.
12
(C) 2006-2014 by the GRASS Development Team
14
This program is free software under the GNU General Public License
15
(>=v2). Read the file COPYING that comes with GRASS for details.
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)
29
from core import globalvar
33
from core.render import Map
34
from vdigit.toolbars import VDigitToolbar
35
from mapdisp.toolbars import MapToolbar, NvizIcons
36
from mapdisp.gprint import PrintOptions
37
from core.gcmd import GError, GMessage, RunCommand
38
from dbmgr.dialogs import DisplayAttributesDialog
39
from core.utils import ListOfCatsToRange, GetLayerNameFromCmd, _
40
from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
41
from core.debug import Debug
42
from core.settings import UserSettings
43
from gui_core.mapdisp import SingleMapFrame
44
from mapwin.base import MapWindowProperties
45
from gui_core.query import QueryDialog, PrepareQueryResults
46
from mapwin.buffered import BufferedMapWindow
47
from mapwin.decorations import TextLayerDialog, \
48
LegendController, BarscaleController, ArrowController
49
from modules.histogram import HistogramFrame
50
from wxplot.histogram import HistogramPlotFrame
51
from wxplot.profile import ProfileFrame
52
from wxplot.scatter import ScatterFrame
53
from mapwin.analysis import ProfileController, MeasureDistanceController, \
55
from gui_core.forms import GUI
56
from core.giface import Notification
58
from mapdisp import statusbar as sb
60
import grass.script as grass
62
from grass.pydispatch.signal import Signal
65
class MapFrame(SingleMapFrame):
66
"""Main frame for map display window. Drawing takes place in
67
child double buffered drawing window.
69
def __init__(self, parent, giface, title = _("GRASS GIS - Map display"),
70
toolbars = ["map"], tree = None, notebook = None, lmgr = None,
71
page = None, Map = Map(), auimgr = None, name = 'MapWindow', **kwargs):
72
"""Main map display window with toolbars, statusbar and
73
2D map window, 3D map window and digitizer.
75
:param toolbars: array of activated toolbars, e.g. ['map', 'digit']
76
:param tree: reference to layer tree
77
:param notebook: control book ID in Layer Manager
78
:param lmgr: Layer Manager
79
:param page: notebook page with layer tree
80
:param map: instance of render.Map
81
:param auimgs: AUI manager
82
:param name: frame name
83
:param kwargs: wx.Frame attributes
85
SingleMapFrame.__init__(self, parent = parent, title = title,
86
Map = Map, auimgr = auimgr, name = name, **kwargs)
89
# Layer Manager object
90
# need by GLWindow (a lot), VDigitWindow (a little bit)
91
self._layerManager = lmgr
92
# Layer Manager layer tree object
93
# used for VDigit toolbar and window and GLWindow
95
# Notebook page holding the layer tree
96
# used only in OnCloseWindow
98
# Layer Manager layer tree notebook
99
# used only in OnCloseWindow
100
self.layerbook = notebook
102
# Emitted when starting (switching to) 3D mode.
103
# Parameter firstTime specifies if 3D was already actived.
104
self.starting3dMode = Signal("MapFrame.starting3dMode")
106
# Emitted when ending (switching from) 3D mode.
107
self.ending3dMode = Signal("MapFrame.ending3dMode")
109
# properties are shared in other objects, so defining here
110
self.mapWindowProperties = MapWindowProperties()
111
self.mapWindowProperties.setValuesFromUserSettings()
116
for toolb in toolbars:
117
self.AddToolbar(toolb)
124
self.statusbarItems = [sb.SbCoordinates,
126
sb.SbCompRegionExtent,
130
sb.SbDisplayGeometry,
135
self.statusbarItemsHiddenInNviz = (sb.SbAlignExtent,
136
sb.SbDisplayGeometry,
141
# create statusbar and its manager
142
statusbar = self.CreateStatusBar(number = 4, style = 0)
143
if globalvar.wxPython3:
144
statusbar.SetMinHeight(24)
145
statusbar.SetStatusWidths([-5, -2, -1, -1])
146
self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar)
148
# fill statusbar manager
149
self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar)
150
self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2))
151
sbRender = sb.SbRender(self, statusbar = statusbar, position = 3)
152
self.statusbarManager.AddStatusbarItem(sbRender)
154
self.statusbarManager.Update()
157
self.Map.updateProgress.connect(self.statusbarManager.SetProgress)
159
# init decoration objects
160
self.decorations = {}
161
self.legend = LegendController(self.Map, self._giface)
162
self.barscale = BarscaleController(self.Map, self._giface)
163
self.arrow = ArrowController(self.Map, self._giface)
164
self.decorations[self.legend.id] = self.legend
165
self.decorations[self.barscale.id] = self.barscale
166
self.decorations[self.arrow.id] = self.arrow
168
self.mapWindowProperties.autoRenderChanged.connect(
170
self.OnRender(None) if value else None)
173
# Init map display (buffered DC & set default cursor)
175
self.MapWindow2D = BufferedMapWindow(self, giface = self._giface,
177
properties=self.mapWindowProperties,
178
overlays=self.decorations)
179
self.MapWindow2D.mapQueried.connect(self.Query)
180
self.MapWindow2D.overlayActivated.connect(self._activateOverlay)
181
self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
182
self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
183
for overlay in (self.legend, self.barscale, self.arrow):
184
overlay.overlayChanged.connect(lambda: self.MapWindow2D.UpdateMap(render=False, renderVector=False))
185
self._setUpMapWindow(self.MapWindow2D)
187
self.MapWindow2D.mouseHandlerUnregistered.connect(self.ResetPointer)
189
self.MapWindow2D.InitZoomHistory()
190
self.MapWindow2D.zoomChanged.connect(self.StatusbarUpdate)
192
self._giface.updateMap.connect(self.MapWindow2D.UpdateMap)
193
# default is 2D display mode
194
self.MapWindow = self.MapWindow2D
195
self.MapWindow.SetNamedCursor('default')
196
# used by vector digitizer
197
self.MapWindowVDigit = None
198
# used by Nviz (3D display mode)
199
self.MapWindow3D = None
202
# initialize region values
204
self._initMap(Map = self.Map)
206
self.toolbars['map'].SelectDefault()
208
# Bind various events
210
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
211
self.Bind(wx.EVT_SIZE, self.OnSize)
214
# Update fancy gui style
216
self._mgr.AddPane(self.MapWindow, wx.aui.AuiPaneInfo().CentrePane().
217
Dockable(False).BestSize((-1,-1)).Name('2d').
218
CloseButton(False).DestroyOnClose(True).
223
# Init print module and classes
225
self.printopt = PrintOptions(self, self.MapWindow)
231
self.dialogs['attributes'] = None
232
self.dialogs['category'] = None
233
self.dialogs['vnet'] = None
234
self.dialogs['query'] = None
236
# initialize layers to query (d.what.vect/rast)
237
self._vectQueryLayers = []
238
self._rastQueryLayers = []
240
self.measureController = None
242
def GetMapWindow(self):
243
return self.MapWindow
245
def SetTitleNumber(self, displayId=1):
246
"""Set map display title"""
248
grassVersion = grass.version()['version']
250
sys.stderr.write(_("Unable to get GRASS version\n"))
253
gisenv = grass.gisenv()
254
title = _("GRASS GIS %(version)s Map Display: %(id)s - Location: %(loc)s@%(mapset)s") % \
255
{'version': grassVersion,
256
'id': str(displayId),
257
'loc': gisenv["LOCATION_NAME"],
258
'mapset': gisenv["MAPSET"]}
262
def _addToolbarVDigit(self):
263
"""Add vector digitizer toolbar
265
from vdigit.main import haveVDigit, VDigit
268
from vdigit import errorMsg
270
self.toolbars['map'].combo.SetValue(_("2D view"))
272
GError(_("Unable to start wxGUI vector digitizer.\n"
273
"Details: %s") % errorMsg, parent = self)
276
if not self.MapWindowVDigit:
277
from vdigit.mapwindow import VDigitWindow
278
self.MapWindowVDigit = VDigitWindow(parent=self, giface=self._giface,
279
properties=self.mapWindowProperties,
280
Map=self.Map, tree=self.tree,
281
lmgr=self._layerManager,
282
overlays=self.decorations)
283
self._setUpMapWindow(self.MapWindowVDigit)
284
self.MapWindowVDigit.digitizingInfo.connect(
286
self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(text))
287
self.MapWindowVDigit.digitizingInfoUnavailable.connect(
289
self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(None))
290
self.MapWindowVDigit.Show()
291
self._mgr.AddPane(self.MapWindowVDigit, wx.aui.AuiPaneInfo().CentrePane().
292
Dockable(False).BestSize((-1,-1)).Name('vdigit').
293
CloseButton(False).DestroyOnClose(True).
296
self._switchMapWindow(self.MapWindowVDigit)
298
if self._mgr.GetPane('2d').IsShown():
299
self._mgr.GetPane('2d').Hide()
300
elif self._mgr.GetPane('3d').IsShown():
301
self._mgr.GetPane('3d').Hide()
302
self._mgr.GetPane('vdigit').Show()
303
self.toolbars['vdigit'] = VDigitToolbar(parent=self, toolSwitcher=self._toolSwitcher,
304
MapWindow = self.MapWindow,
305
digitClass=VDigit, giface=self._giface)
306
self.MapWindowVDigit.SetToolbar(self.toolbars['vdigit'])
308
self._mgr.AddPane(self.toolbars['vdigit'],
309
wx.aui.AuiPaneInfo().
310
Name("vdigittoolbar").Caption(_("Vector Digitizer Toolbar")).
311
ToolbarPane().Top().Row(1).
312
LeftDockable(False).RightDockable(False).
313
BottomDockable(False).TopDockable(True).
314
CloseButton(False).Layer(2).
315
BestSize((self.toolbars['vdigit'].GetBestSize())))
316
# change mouse to draw digitized line
317
self.MapWindow.mouse['box'] = "point"
318
self.MapWindow.zoomtype = 0
319
self.MapWindow.pen = wx.Pen(colour = 'red', width = 2, style = wx.SOLID)
320
self.MapWindow.polypen = wx.Pen(colour = 'green', width = 2, style = wx.SOLID)
323
"""Add 3D view mode window
325
from nviz.main import haveNviz, GLWindow, errorMsg
327
# check for GLCanvas and OpenGL
329
self.toolbars['map'].combo.SetValue(_("2D view"))
330
GError(parent = self,
331
message = _("Unable to switch to 3D display mode.\nThe Nviz python extension "
332
"was not found or loaded properly.\n"
333
"Switching back to 2D display mode.\n\nDetails: %s" % errorMsg))
336
# here was disabling 3D for other displays, now done on starting3dMode
338
self.toolbars['map'].Enable2D(False)
339
# add rotate tool to map toolbar
340
self.toolbars['map'].InsertTool((('rotate', NvizIcons['rotate'],
341
self.OnRotate, wx.ITEM_CHECK, 7),)) # 7 is position
342
self._toolSwitcher.AddToolToGroup(group='mouseUse', toolbar=self.toolbars['map'],
343
tool=self.toolbars['map'].rotate)
344
self.toolbars['map'].InsertTool((('flyThrough', NvizIcons['flyThrough'],
345
self.OnFlyThrough, wx.ITEM_CHECK, 8),))
346
self._toolSwitcher.AddToolToGroup(group='mouseUse', toolbar=self.toolbars['map'],
347
tool=self.toolbars['map'].flyThrough)
348
self.toolbars['map'].ChangeToolsDesc(mode2d = False)
351
self.statusbarManager.HideStatusbarChoiceItemsByClass(self.statusbarItemsHiddenInNviz)
352
self.statusbarManager.SetMode(0)
355
self.MapWindow.EraseMap()
357
self._giface.WriteCmdLog(_("Starting 3D view mode..."), notification=Notification.HIGHLIGHT)
358
self.SetStatusText(_("Please wait, loading data..."), 0)
361
if not self.MapWindow3D:
362
self.MapWindow3D = GLWindow(self, giface = self._giface, id = wx.ID_ANY, frame = self,
363
Map = self.Map, tree = self.tree, lmgr = self._layerManager)
364
self._setUpMapWindow(self.MapWindow3D)
365
self.MapWindow3D.mapQueried.connect(self.Query)
366
self._switchMapWindow(self.MapWindow3D)
367
self.MapWindow.SetNamedCursor('default')
369
# here was AddNvizTools in lmgr
370
self.starting3dMode.emit(firstTime=True)
372
# switch from MapWindow to MapWindowGL
373
self._mgr.GetPane('2d').Hide()
374
self._mgr.AddPane(self.MapWindow3D, wx.aui.AuiPaneInfo().CentrePane().
375
Dockable(False).BestSize((-1,-1)).Name('3d').
376
CloseButton(False).DestroyOnClose(True).
379
self.MapWindow3D.Show()
380
self.MapWindow3D.ResetViewHistory()
381
self.MapWindow3D.UpdateView(None)
382
self.MapWindow3D.overlayActivated.connect(self._activateOverlay)
383
self.MapWindow3D.overlayHidden.connect(self._hideOverlay)
384
self.legend.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
386
self._switchMapWindow(self.MapWindow3D)
387
os.environ['GRASS_REGION'] = self.Map.SetRegion(windres = True, windres3 = True)
388
self.MapWindow3D.GetDisplay().Init()
389
del os.environ['GRASS_REGION']
391
# switch from MapWindow to MapWindowGL
392
self._mgr.GetPane('2d').Hide()
393
self._mgr.GetPane('3d').Show()
395
# here was AddNvizTools in lmgr and updating of pages
396
self.starting3dMode.emit(firstTime=False)
398
self.MapWindow3D.ResetViewHistory()
400
self._giface.updateMap.disconnect(self.MapWindow2D.UpdateMap)
401
self._giface.updateMap.connect(self.MapWindow3D.UpdateMap)
402
self.MapWindow3D.overlays = self.MapWindow2D.overlays
403
self.MapWindow3D.textdict = self.MapWindow2D.textdict
404
# update overlays needs to be called after because getClientSize
405
# is called during update and it must give reasonable values
406
wx.CallAfter(self.MapWindow3D.UpdateOverlays)
408
self.SetStatusText("", 0)
411
def Disable3dMode(self):
412
"""Disables 3D mode (NVIZ) in user interface."""
413
# TODO: this is broken since item is removed but switch is drived by index
414
if '3D' in self.toolbars['map'].combo.GetString(1):
415
self.toolbars['map'].combo.Delete(1)
417
def RemoveNviz(self):
418
"""Restore 2D view"""
420
self.toolbars['map'].RemoveTool(self.toolbars['map'].rotate)
421
self.toolbars['map'].RemoveTool(self.toolbars['map'].flyThrough)
422
except AttributeError:
426
self.statusbarManager.ShowStatusbarChoiceItemsByClass(self.statusbarItemsHiddenInNviz)
427
self.statusbarManager.SetMode(UserSettings.Get(group = 'display',
428
key = 'statusbarMode',
429
subkey = 'selection'))
430
self.SetStatusText(_("Please wait, unloading data..."), 0)
431
# unloading messages from library cause highlight anyway
432
self._giface.WriteCmdLog(_("Switching back to 2D view mode..."),
433
notification=Notification.NO_NOTIFICATION)
435
self.MapWindow3D.OnClose(event = None)
436
# switch from MapWindowGL to MapWindow
437
self._mgr.GetPane('2d').Show()
438
self._mgr.GetPane('3d').Hide()
440
self._switchMapWindow(self.MapWindow2D)
441
# here was RemoveNvizTools form lmgr
442
self.ending3dMode.emit()
444
self.MapWindow2D.overlays = self.MapWindow3D.overlays
445
self.MapWindow2D.textdict = self.MapWindow3D.textdict
446
except AttributeError:
448
# TODO: here we end because self.MapWindow3D is None for a while
449
self._giface.updateMap.disconnect(self.MapWindow3D.UpdateMap)
450
self._giface.updateMap.connect(self.MapWindow2D.UpdateMap)
451
self.legend.overlayChanged.disconnect(self.MapWindow3D.UpdateOverlays)
453
self.MapWindow.UpdateMap()
455
self.GetMapToolbar().SelectDefault()
457
def AddToolbar(self, name, fixed = False):
458
"""Add defined toolbar to the window
460
Currently recognized toolbars are:
461
- 'map' - basic map toolbar
462
- 'vdigit' - vector digitizer
464
:param name: toolbar to add
465
:param fixed: fixed toolbar
469
self.toolbars['map'] = MapToolbar(self, toolSwitcher=self._toolSwitcher)
471
self._mgr.AddPane(self.toolbars['map'],
472
wx.aui.AuiPaneInfo().
473
Name("maptoolbar").Caption(_("Map Toolbar")).
474
ToolbarPane().Top().Name('mapToolbar').
475
LeftDockable(False).RightDockable(False).
476
BottomDockable(False).TopDockable(True).
477
CloseButton(False).Layer(2).
478
BestSize((self.toolbars['map'].GetBestSize())))
481
elif name == "vdigit":
482
self.toolbars['map'].combo.SetValue(_("Digitize"))
483
self._addToolbarVDigit()
486
self.toolbars['map'].combo.Disable()
490
def RemoveToolbar (self, name):
491
"""Removes defined toolbar from the window
494
Only hide, activate by calling AddToolbar()
496
# cannot hide main toolbar
500
self._mgr.DetachPane(self.toolbars[name])
501
self._toolSwitcher.RemoveToolbarFromGroup('mouseUse', self.toolbars[name])
502
self.toolbars[name].Destroy()
503
self.toolbars.pop(name)
506
self._mgr.GetPane('vdigit').Hide()
507
self._mgr.GetPane('2d').Show()
508
self._switchMapWindow(self.MapWindow2D)
510
self.toolbars['map'].combo.SetValue(_("2D view"))
511
self.toolbars['map'].Enable2D(True)
515
def IsPaneShown(self, name):
516
"""Check if pane (toolbar, mapWindow ...) of given name is currently shown"""
517
if self._mgr.GetPane(name).IsOk():
518
return self._mgr.GetPane(name).IsShown()
521
def RemoveQueryLayer(self):
522
"""Removes temporary map layers (queries)"""
523
qlayer = self.GetMap().GetListOfLayers(name = globalvar.QUERYLAYER)
525
self.GetMap().DeleteLayer(layer)
527
def OnRender(self, event):
528
"""Re-render map composition (each map layer)
530
self.RemoveQueryLayer()
532
# deselect features in vdigit
533
if self.GetToolbar('vdigit'):
534
if self.MapWindow.digit:
535
self.MapWindow.digit.GetDisplay().SetSelected([])
536
self.MapWindow.UpdateMap(render = True, renderVector = True)
538
self.MapWindow.UpdateMap(render = True)
541
self.StatusbarUpdate()
543
def OnPointer(self, event):
544
"""Pointer button clicked
546
self.MapWindow.SetModePointer()
548
if self.GetToolbar('vdigit'):
549
self.toolbars['vdigit'].action['id'] = -1
550
self.toolbars['vdigit'].action['desc']=''
552
def OnRotate(self, event):
555
self.MapWindow.mouse['use'] = "rotate"
558
self.MapWindow.SetNamedCursor('hand')
560
def OnFlyThrough(self, event):
563
self.MapWindow.mouse['use'] = "fly"
566
self.MapWindow.SetNamedCursor('hand')
567
self.MapWindow.SetFocus()
569
def SaveToFile(self, event):
572
filetype, ltype = self._prepareSaveToFile()
577
dlg = ImageSizeDialog(self)
579
if dlg.ShowModal() != wx.ID_OK:
582
width, height = dlg.GetValues()
586
dlg = wx.FileDialog(parent = self,
587
message = _("Choose a file name to save the image "
588
"(no need to add extension)"),
590
style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
592
if dlg.ShowModal() == wx.ID_OK:
598
base, ext = os.path.splitext(path)
599
fileType = ltype[dlg.GetFilterIndex()]['type']
600
extType = ltype[dlg.GetFilterIndex()]['ext']
602
path = base + '.' + extType
604
self.MapWindow.SaveToFile(path, fileType,
609
def DOutFile(self, command):
610
"""Saves map to image by running d.out.file from gui or d.mon.
611
Command is expected to be validated by parser.
613
filetype, ltype = self._prepareSaveToFile()
616
width, height = self.MapWindow.GetClientSize()
617
for param in command[1:]:
618
p, val = param.split('=')
619
if p == 'format': # must be there
620
if self.IsPaneShown('3d'):
624
if p == 'output': # must be there
627
width, height = val.split(',')
629
base, ext = os.path.splitext(name)
631
name = base + '.' + extType
632
elif ext[1:] != extType:
635
if self.IsPaneShown('3d'):
638
bitmapType = wx.BITMAP_TYPE_PNG # default type
640
if each['ext'] == extType:
641
bitmapType = each['type']
643
self.MapWindow.SaveToFile(name, bitmapType, int(width), int(height))
645
def DOutFileOptData(self, dcmd, layer, params, propwin):
646
"""Dummy function which is called when d.out.file is called
647
and returns parsed and validated command which is then passed
648
to DOutFile method."""
654
def DToRast(self, command):
655
"""Saves currently loaded composition of layers as a raster map.
657
if self.IsPaneShown('3d'):
658
self._giface.WriteError(_('d.to.rast can be used only in 2D mode.'))
662
for param in command[1:]:
664
p, val = param.split('=')
668
if param.startswith('--overwrite'):
674
tmpName = 'd_to_rast_tmp'
675
pngFile = grass.tempfile(create=False) + '.png'
676
dOutFileCmd = ['d.out.file', 'output=' + pngFile, 'format=png']
677
self.DOutFile(dOutFileCmd)
678
# import back as red, green, blue rasters
679
returncode, messages = RunCommand('r.in.gdal', flags='o', input=pngFile, output=tmpName,
680
quiet=True, overwrite=overwrite, getErrorMsg=True)
681
if not returncode == 0:
682
self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
684
# set region for composite
685
grass.use_temp_region()
686
returncode, messages = RunCommand('g.region', raster=tmpName + '.red',
687
quiet=True, getErrorMsg=True)
688
if not returncode == 0:
689
grass.del_temp_region()
690
self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
693
returncode, messages = RunCommand('r.composite', red=tmpName + '.red',
694
green=tmpName + '.green', blue=tmpName + '.blue',
695
output=outputRaster, quiet=True,
696
overwrite=overwrite, getErrorMsg=True)
697
grass.del_temp_region()
698
RunCommand('g.remove', type='raster', flags='f', quiet=True,
699
name=[tmpName + '.red', tmpName + '.green', tmpName + '.blue'])
700
if not returncode == 0:
701
self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
702
grass.try_remove(pngFile)
705
# alignExtent changes only region variable
706
oldRegion = self.GetMap().GetCurrentRegion().copy()
707
self.GetMap().AlignExtentFromDisplay()
708
region = self.GetMap().GetCurrentRegion().copy()
709
self.GetMap().region.update(oldRegion)
710
RunCommand('r.region', map=outputRaster, n=region['n'], s=region['s'],
711
e=region['e'], w=region['w'], quiet=True)
713
grass.try_remove(pngFile)
715
def DToRastOptData(self, dcmd, layer, params, propwin):
716
"""Dummy function which is called when d.to.rast is called
717
and returns parsed and validated command which is then passed
718
to DToRast method."""
724
def _prepareSaveToFile(self):
725
"""Get wildcards and format extensions."""
726
if self.IsPaneShown('3d'):
727
filetype = "TIF file (*.tif)|*.tif|PPM file (*.ppm)|*.ppm"
728
ltype = [{ 'ext' : 'tif', 'type' : 'tif' },
729
{ 'ext' : 'ppm', 'type' : 'ppm' }]
731
img = self.MapWindow.img
733
GMessage(parent = self,
734
message = _("Nothing to render (empty map). Operation canceled."))
736
filetype, ltype = GetImageHandlers(img)
737
return filetype, ltype
739
def PrintMenu(self, event):
741
Print options and output menu for map display
743
printmenu = wx.Menu()
744
# Add items to the menu
745
setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup'))
746
printmenu.AppendItem(setup)
747
self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
749
preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview'))
750
printmenu.AppendItem(preview)
751
self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
753
doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display'))
754
printmenu.AppendItem(doprint)
755
self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
757
# Popup the menu. If an item is selected then its handler
758
# will be called before PopupMenu returns.
759
self.PopupMenu(printmenu)
762
def OnCloseWindow(self, event):
764
Also close associated layer tree page
766
Debug.msg(2, "MapFrame.OnCloseWindow(): function starts")
770
# close edited map and 3D tools properly
771
if self.GetToolbar('vdigit'):
772
maplayer = self.toolbars['vdigit'].GetLayer()
774
self.toolbars['vdigit'].OnExit()
775
if self.IsPaneShown('3d'):
778
if not self._layerManager:
781
pgnum = self.layerbook.GetPageIndex(self.page)
783
self.layerbook.DeletePage(pgnum)
784
Debug.msg(2, "MapFrame.OnCloseWindow(): function ends")
786
def Query(self, x, y):
787
"""Query selected layers.
789
:param x,y: coordinates
791
if self._vectQueryLayers or self._rastQueryLayers:
792
rast = self._rastQueryLayers
793
vect = self._vectQueryLayers
795
layers = self._giface.GetLayerList().GetSelectedLayers(checkedOnly=False)
799
name, found = GetLayerNameFromCmd(layer.cmd)
802
ltype = layer.maplayer.GetType()
803
if ltype == 'raster':
805
elif ltype in ('rgb', 'his'):
806
for iname in name.split('\n'):
808
elif ltype in ('vector', 'thememap', 'themechart'):
811
# check for vector maps open to be edited
812
digitToolbar = self.GetToolbar('vdigit')
814
lmap = digitToolbar.GetLayer().GetName()
817
self._giface.WriteWarning(_("Vector map <%s> "
818
"opened for editing - skipped.") % lmap)
821
if not (rast + vect):
822
GMessage(parent = self,
823
message = _('No raster or vector map layer selected for querying.'))
826
# set query snap distance for v.what at map unit equivalent of 10 pixels
827
qdist = 10.0 * ((self.Map.region['e'] - self.Map.region['w']) / self.Map.width)
829
# TODO: replace returning None by exception or so
831
east, north = self.MapWindow.Pixel2Cell((x, y))
835
if not self.IsPaneShown('3d'):
836
self.QueryMap(east, north, qdist, rast, vect)
839
self.MapWindow.QuerySurface(x, y)
841
self.QueryMap(east, north, qdist, rast = [], vect = vect)
843
def SetQueryLayersAndActivate(self, ltype, maps):
844
"""Activate query mode and set layers to query.
845
This method is used for querying in d.mon using d.what.rast/vect"""
846
self.toolbars['map'].SelectTool(self.toolbars['map'].query)
847
if ltype == 'vector':
848
self._vectQueryLayers = maps
849
elif ltype == 'raster':
850
self._rastQueryLayers = maps
852
def QueryMap(self, east, north, qdist, rast, vect):
853
"""Query raster or vector map layers by r/v.what
855
:param east,north: coordinates
856
:param qdist: query distance
857
:param rast: raster map names
858
:param vect: vector map names
860
Debug.msg(1, "QueryMap(): raster=%s vector=%s" % (','.join(rast),
863
# use display region settings instead of computation region settings
864
self.tmpreg = os.getenv("GRASS_REGION")
865
os.environ["GRASS_REGION"] = self.Map.SetRegion(windres = False)
870
rastQuery = grass.raster_what(map=rast, coord=(east, north))
872
encoding = UserSettings.Get(group='atm', key='encoding', subkey='value')
874
vectQuery = grass.vector_what(map=vect, coord=(east, north), distance=qdist,
876
except grass.ScriptError:
878
message=_("Failed to query vector map(s) <{maps}>. "
879
"Check database settings and topology.").format(maps=','.join(vect)))
881
if 'Id' in vectQuery:
882
self._queryHighlight(vectQuery)
884
result = rastQuery + vectQuery
885
result = PrepareQueryResults(coordinates = (east, north), result = result)
886
if self.dialogs['query']:
887
self.dialogs['query'].Raise()
888
self.dialogs['query'].SetData(result)
890
self.dialogs['query'] = QueryDialog(parent = self, data = result)
891
self.dialogs['query'].Bind(wx.EVT_CLOSE, self._oncloseQueryDialog)
892
self.dialogs['query'].redirectOutput.connect(self._onRedirectQueryOutput)
893
self.dialogs['query'].Show()
895
def _oncloseQueryDialog(self, event):
896
self.dialogs['query'] = None
897
self._vectQueryLayers = []
898
self._rastQueryLayers = []
901
def _onRedirectQueryOutput(self, output, style='log'):
902
"""Writes query output into console"""
904
self._giface.WriteLog(output, notification=Notification.MAKE_VISIBLE)
906
self._giface.WriteCmdLog(output)
908
def _queryHighlight(self, vectQuery):
909
"""Highlight category from query."""
911
for res in vectQuery:
912
cats = {res['Layer']: [res['Category']]}
915
qlayer = self.Map.GetListOfLayers(name = globalvar.QUERYLAYER)[0]
919
if not (cats and name):
921
self.Map.DeleteLayer(qlayer)
922
self.MapWindow.UpdateMap(render = False, renderVector = False)
925
if not self.IsPaneShown('3d') and self.IsAutoRendered():
926
# highlight feature & re-draw map
928
qlayer.SetCmd(self.AddTmpVectorMapLayer(name, cats,
932
qlayer = self.AddTmpVectorMapLayer(name, cats, useId = False)
934
# set opacity based on queried layer
936
# opacity = layer.maplayer.GetOpacity(float = True)
937
# qlayer.SetOpacity(opacity)
939
self.MapWindow.UpdateMap(render = False, renderVector = False)
941
def _QueryMapDone(self):
942
"""Restore settings after querying (restore GRASS_REGION)
944
if hasattr(self, "tmpreg"):
946
os.environ["GRASS_REGION"] = self.tmpreg
947
elif 'GRASS_REGION' in os.environ:
948
del os.environ["GRASS_REGION"]
949
elif 'GRASS_REGION' in os.environ:
950
del os.environ["GRASS_REGION"]
952
if hasattr(self, "tmpreg"):
955
def OnQuery(self, event):
956
"""Query tools menu"""
957
self.MapWindow.mouse['use'] = "query"
958
self.MapWindow.mouse['box'] = "point"
959
self.MapWindow.zoomtype = 0
962
self.MapWindow.SetNamedCursor('cross')
964
def AddTmpVectorMapLayer(self, name, cats, useId = False, addLayer = True):
965
"""Add temporal vector map layer to map composition
967
:param name: name of map layer
968
:param useId: use feature id instead of category
970
# color settings from ATM
971
color = UserSettings.Get(group = 'atm', key = 'highlight', subkey = 'color')
972
colorStr = str(color[0]) + ":" + \
973
str(color[1]) + ":" + \
976
# icon used in vector display and its size
979
# here we know that there is one selected layer and it is vector
980
vparam = self._giface.GetLayerList().GetSelectedLayers()[0].cmd
983
parg,pval = p.split('=', 1)
984
if parg == 'icon': icon = pval
985
elif parg == 'size': size = float(pval)
989
"color=%s" % colorStr,
990
"fill_color=%s" % colorStr,
991
"width=%d" % UserSettings.Get(group = 'atm', key = 'highlight', subkey = 'width')]
993
pattern.append('icon=%s' % icon)
995
pattern.append('size=%i' % size)
1000
cmd.append('cats=%s' % str(cats))
1003
for layer in cats.keys():
1004
cmd.append(copy.copy(pattern))
1006
cmd[-1].append("layer=%d" % layer)
1007
cmd[-1].append("cats=%s" % ListOfCatsToRange(lcats))
1011
return self.Map.AddLayer(ltype = 'vector', name = globalvar.QUERYLAYER, command = cmd,
1012
active = True, hidden = True, opacity = 1.0)
1014
return self.Map.AddLayer(ltype = 'command', name = globalvar.QUERYLAYER, command = cmd,
1015
active = True, hidden = True, opacity = 1.0)
1019
def OnMeasureDistance(self, event):
1020
self._onMeasure(MeasureDistanceController)
1022
def OnMeasureArea(self, event):
1023
self._onMeasure(MeasureAreaController)
1025
def _onMeasure(self, controller):
1026
"""Starts measurement mode.
1028
:param controller: measurement class (MeasureDistanceController, MeasureAreaController)
1030
self.measureController = controller(self._giface, mapWindow=self.GetMapWindow())
1031
# assure that the mode is ended and lines are cleared whenever other tool is selected
1032
self._toolSwitcher.toggleToolChanged.connect(lambda: self.measureController.Stop())
1033
self.measureController.Start()
1035
def OnProfile(self, event):
1036
"""Launch profile tool
1039
layers = self._giface.GetLayerList().GetSelectedLayers()
1040
for layer in layers:
1041
if layer.type == 'raster':
1042
rasters.append(layer.maplayer.name)
1043
self.Profile(rasters=rasters)
1045
def Profile(self, rasters=None):
1046
"""Launch profile tool"""
1047
self.profileController = ProfileController(self._giface,
1048
mapWindow=self.GetMapWindow())
1049
win = ProfileFrame(parent=self, rasterList=rasters,
1050
units=self.Map.projinfo['units'],
1051
controller=self.profileController)
1053
# Open raster select dialog to make sure that a raster (and
1054
# the desired raster) is selected to be profiled
1055
win.OnSelectRaster(None)
1057
def OnHistogramPyPlot(self, event):
1058
"""Init PyPlot histogram display canvas and tools
1062
for layer in self._giface.GetLayerList().GetSelectedLayers():
1063
if layer.maplayer.GetType() == 'raster':
1064
raster.append(layer.maplayer.GetName())
1066
win = HistogramPlotFrame(parent = self, rasterList = raster)
1067
win.CentreOnParent()
1070
def OnScatterplot(self, event):
1071
"""Init PyPlot scatterplot display canvas and tools
1075
for layer in self._giface.GetLayerList().GetSelectedLayers():
1076
if layer.maplayer.GetType() == 'raster':
1077
raster.append(layer.maplayer.GetName())
1079
win = ScatterFrame(parent = self, rasterList = raster)
1081
win.CentreOnParent()
1083
# Open raster select dialog to make sure that at least 2 rasters (and the desired rasters)
1084
# are selected to be plotted
1085
win.OnSelectRaster(None)
1087
def OnHistogram(self, event):
1088
"""Init histogram display canvas and tools
1090
win = HistogramFrame(self, giface=self._giface)
1092
win.CentreOnParent()
1097
def _activateOverlay(self, overlayId):
1098
"""Launch decoration dialog according to overlay id.
1100
:param overlayId: id of overlay
1103
self.OnAddText(None)
1104
elif overlayId == 0:
1105
self.AddLegend(cmd=self.legend.cmd, showDialog=True)
1106
elif overlayId == 1:
1107
self.AddBarscale(showDialog=True)
1108
elif overlayId == 2:
1109
self.AddArrow(showDialog=True)
1111
def _hideOverlay(self, overlayId):
1114
:param overlayId: id of overlay
1116
self.decorations[overlayId].Hide()
1118
def AddBarscale(self, cmd=None, showDialog=None):
1119
"""Handler for scale bar map decoration menu selection."""
1120
if self.IsPaneShown('3d'):
1121
self.MapWindow3D.SetDrawScalebar((70, 70))
1124
if self.barscale.IsShown() and showDialog is None:
1125
self.barscale.Hide()
1129
self.barscale.cmd = cmd
1132
self.barscale.Show()
1135
# Decoration overlay control dialog
1136
if self.barscale.dialog:
1137
if self.barscale.dialog.IsShown():
1138
self.barscale.dialog.SetFocus()
1139
self.barscale.dialog.Raise()
1141
self.barscale.dialog.Show()
1143
# If location is latlon, only display north arrow (scale won't work)
1144
# proj = self.Map.projinfo['proj']
1146
# barcmd = 'd.barscale -n'
1148
# barcmd = 'd.barscale'
1150
# decoration overlay control dialog
1151
GUI(parent=self, giface=self._giface, show=True,
1152
modal=False).ParseCommand(self.barscale.cmd,
1153
completed=(self.barscale.GetOptData, None, None))
1155
self.MapWindow.mouse['use'] = 'pointer'
1157
def AddLegend(self, cmd=None, showDialog=None):
1158
"""Handler for legend map decoration menu selection."""
1159
if self.legend.IsShown() and showDialog is None:
1163
self.legend.cmd = cmd
1165
layers = self._giface.GetLayerList().GetSelectedLayers()
1166
for layer in layers:
1167
if layer.type == 'raster':
1170
for i, legendParam in enumerate(self.legend.cmd[1:]):
1172
param_val = legendParam.split('=')
1173
if len(param_val) != 2:
1175
param, val = param_val
1176
if param == 'raster':
1177
self.legend.cmd[idx] = 'raster={rast}'.format(rast=layer.maplayer.name)
1179
elif param in ('use', 'range'):
1180
# clear range or use to avoid problems
1181
del self.legend.cmd[idx]
1183
if not isMap: # for the first time
1184
self.legend.cmd.append('raster=%s' % layer.maplayer.name)
1187
if not showDialog and self.legend.CmdIsValid():
1191
# Decoration overlay control dialog
1192
# always create new one to avoid problem when switching between maps
1193
if self.legend.dialog:
1194
if self.legend.dialog.IsShown():
1195
self.legend.dialog.SetFocus()
1196
self.legend.dialog.Raise()
1198
self.legend.dialog.Destroy()
1199
self.legend.dialog = None
1200
if not self.legend.dialog:
1201
GUI(parent=self, giface=self._giface, show=True,
1202
modal=False).ParseCommand(self.legend.cmd,
1203
completed=(self.legend.GetOptData, None, None))
1205
self.MapWindow.mouse['use'] = 'pointer'
1207
def AddArrow(self, cmd=None, showDialog=None):
1208
"""Handler for north arrow menu selection."""
1209
if self.IsPaneShown('3d'):
1210
# here was opening of appearance page of nviz notebook
1211
# but now moved to MapWindow3D where are other problematic nviz calls
1212
self.MapWindow3D.SetDrawArrow((70, 70))
1215
if self.arrow.IsShown() and showDialog is None:
1219
self.arrow.cmd = cmd
1225
# Decoration overlay control dialog
1226
if self.arrow.dialog:
1227
if self.arrow.dialog.IsShown():
1228
self.arrow.dialog.SetFocus()
1229
self.arrow.dialog.Raise()
1231
self.arrow.dialog.Show()
1233
GUI(parent=self, giface=self._giface, show=True,
1234
modal=False).ParseCommand(self.arrow.cmd,
1235
completed=(self.arrow.GetOptData, None, None))
1237
self.MapWindow.mouse['use'] = 'pointer'
1239
def OnAddText(self, event):
1240
"""Handler for text decoration menu selection.
1242
if self.MapWindow.dragid > -1:
1243
id = self.MapWindow.dragid
1244
self.MapWindow.dragid = -1
1246
# index for overlay layer in render
1247
if len(self.MapWindow.textdict.keys()) > 0:
1248
id = max(self.MapWindow.textdict.keys()) + 1
1252
self.dialogs['text'] = TextLayerDialog(parent = self, ovlId = id,
1253
title = _('Add text layer'),
1255
self.dialogs['text'].CenterOnParent()
1257
# If OK button pressed in decoration control dialog
1258
if self.dialogs['text'].ShowModal() == wx.ID_OK:
1259
text = self.dialogs['text'].GetValues()['text']
1260
active = self.dialogs['text'].GetValues()['active']
1262
# delete object if it has no text or is not active
1263
if text == '' or active == False:
1265
self.MapWindow2D.pdc.ClearId(id)
1266
self.MapWindow2D.pdc.RemoveId(id)
1267
del self.MapWindow.textdict[id]
1268
if self.IsPaneShown('3d'):
1269
self.MapWindow3D.UpdateOverlays()
1270
self.MapWindow.UpdateMap()
1272
self.MapWindow2D.UpdateMap(render = False, renderVector = False)
1278
self.MapWindow.textdict[id] = self.dialogs['text'].GetValues()
1280
if self.IsPaneShown('3d'):
1281
self.MapWindow3D.UpdateOverlays()
1282
self.MapWindow3D.UpdateMap()
1284
self.MapWindow2D.pdc.ClearId(id)
1285
self.MapWindow2D.pdc.SetId(id)
1286
self.MapWindow2D.UpdateMap(render = False, renderVector = False)
1288
self.MapWindow.mouse['use'] = 'pointer'
1290
def GetOptData(self, dcmd, type, params, propwin):
1291
"""Callback method for decoration overlay command generated by
1292
dialog created in menuform.py
1294
# Reset comand and rendering options in render.Map. Always render decoration.
1295
# Showing/hiding handled by PseudoDC
1296
self.Map.ChangeOverlay(ovltype = type, type = 'overlay', name = '', command = dcmd,
1297
active = True, render = False)
1298
self.params[type] = params
1299
self.propwin[type] = propwin
1301
def OnZoomToMap(self, event):
1302
"""Set display extents to match selected raster (including
1303
NULLs) or vector map.
1305
Debug.msg(3, "MapFrame.OnZoomToMap()")
1307
if self.IsStandalone():
1308
layers = self.MapWindow.GetMap().GetListOfLayers(active = False)
1310
self.MapWindow.ZoomToMap(layers = layers)
1312
def OnZoomToRaster(self, event):
1313
"""Set display extents to match selected raster map (ignore NULLs)
1315
self.MapWindow.ZoomToMap(ignoreNulls = True)
1317
def OnZoomToSaved(self, event):
1318
"""Set display geometry to match extents in
1321
self.MapWindow.SetRegion(zoomOnly=True)
1323
def OnSetDisplayToWind(self, event):
1324
"""Set computational region (WIND file) to match display
1327
self.MapWindow.DisplayToWind()
1329
def OnSetWindToRegion(self, event):
1330
"""Set computational region (WIND file) from named region
1333
self.MapWindow.SetRegion(zoomOnly=False)
1335
def OnSetExtentToWind(self, event):
1336
"""Set compulational region extent interactively"""
1337
self.MapWindow.SetModeDrawRegion()
1339
def OnSaveDisplayRegion(self, event):
1340
"""Save display extents to named region file.
1342
self.MapWindow.SaveRegion(display = True)
1344
def OnSaveWindRegion(self, event):
1345
"""Save computational region to named region file.
1347
self.MapWindow.SaveRegion(display = False)
1349
def OnZoomMenu(self, event):
1352
zoommenu = wx.Menu()
1354
for label, handler in ((_('Zoom to default region'), self.OnZoomToDefault),
1355
(_('Zoom to saved region'), self.OnZoomToSaved),
1357
(_('Set computational region extent from display'), self.OnSetDisplayToWind),
1358
(_('Set computational region extent interactively'), self.OnSetExtentToWind),
1359
(_('Set computational region from named region'), self.OnSetWindToRegion),
1361
(_('Save display geometry to named region'), self.OnSaveDisplayRegion),
1362
(_('Save computational region to named region'), self.OnSaveWindRegion)):
1364
mid = wx.MenuItem(zoommenu, wx.ID_ANY, label)
1365
zoommenu.AppendItem(mid)
1366
self.Bind(wx.EVT_MENU, handler, mid)
1368
zoommenu.AppendSeparator()
1370
# Popup the menu. If an item is selected then its handler will
1371
# be called before PopupMenu returns.
1372
self.PopupMenu(zoommenu)
1375
def SetProperties(self, render = False, mode = 0, showCompExtent = False,
1376
constrainRes = False, projection = False, alignExtent = True):
1377
"""Set properies of map display window"""
1378
self.mapWindowProperties.autoRender = render
1379
self.statusbarManager.SetMode(mode)
1380
self.StatusbarUpdate()
1381
self.mapWindowProperties.showRegion = showCompExtent
1382
self.mapWindowProperties.alignExtent = alignExtent
1383
self.mapWindowProperties.resolution = constrainRes
1384
self.SetProperty('projection', projection)
1386
def IsStandalone(self):
1387
"""Check if Map display is standalone
1391
# TODO: once it is removed from 2 places in vdigit it can be deleted
1392
# here and also in base class and other classes in the tree (hopefully)
1393
# and one place here still uses IsStandalone
1394
Debug.msg(1, "MapFrame.IsStandalone(): Method IsStandalone is"
1395
"depreciated, use some general approach instead such as"
1396
" Signals or giface")
1397
if self._layerManager:
1402
def GetLayerManager(self):
1403
"""Get reference to Layer Manager
1405
:return: window reference
1406
:return: None (if standalone)
1410
Debug.msg(1, "MapFrame.GetLayerManager(): Method GetLayerManager is"
1411
"depreciated, use some general approach instead such as"
1412
" Signals or giface")
1413
return self._layerManager
1415
def GetMapToolbar(self):
1416
"""Returns toolbar with zooming tools"""
1417
return self.toolbars['map']
1419
def OnVNet(self, event):
1420
"""Dialog for v.net* modules
1422
if self.dialogs['vnet']:
1423
self.dialogs['vnet'].Raise()
1426
from vnet.dialogs import VNETDialog
1427
self.dialogs['vnet'] = VNETDialog(parent=self, giface=self._giface)
1428
self.dialogs['vnet'].CenterOnScreen()
1429
self.dialogs['vnet'].Show()
1431
def ResetPointer(self):
1432
"""Sets pointer mode.
1434
Sets pointer and toggles it (e.g. after unregistration of mouse
1437
self.GetMapToolbar().SelectDefault()
1439
def _switchMapWindow(self, map_win):
1440
"""Notifies activated and disactivated map_wins."""
1441
self.MapWindow.DisactivateWin()
1442
map_win.ActivateWin()
1444
self.MapWindow = map_win