mapdisplay.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. """!
  2. @package gcp.mapdisplay
  3. @brief Display to manage ground control points with two toolbars, one
  4. for various display management functions, one for manipulating GCPs.
  5. Classes:
  6. - mapdisplay::MapFrame
  7. (C) 2006-2011 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Markus Metz
  11. """
  12. import os
  13. import math
  14. import platform
  15. from core import globalvar
  16. import wx
  17. import wx.aui
  18. from mapdisp.toolbars import MapToolbar
  19. from gcp.toolbars import GCPDisplayToolbar, GCPManToolbar
  20. from mapdisp.gprint import PrintOptions
  21. from core.gcmd import GMessage
  22. from core.utils import _
  23. from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
  24. from gui_core.mapdisp import SingleMapFrame
  25. from core.settings import UserSettings
  26. from mapwin.buffered import BufferedMapWindow
  27. from mapwin.base import MapWindowProperties
  28. from gui_core.toolbars import ToolSwitcher
  29. import mapdisp.statusbar as sb
  30. import gcp.statusbar as sbgcp
  31. # for standalone app
  32. cmdfilename = None
  33. class MapFrame(SingleMapFrame):
  34. """!Main frame for map display window. Drawing takes place in
  35. child double buffered drawing window.
  36. """
  37. def __init__(self, parent, giface,
  38. title=_("GRASS GIS Manage Ground Control Points"),
  39. toolbars=["gcpdisp"], Map=None, auimgr=None,
  40. name='GCPMapWindow', **kwargs):
  41. """!Main map display window with toolbars, statusbar and
  42. DrawWindow
  43. @param giface GRASS interface instance
  44. @param title window title
  45. @param toolbars array of activated toolbars, e.g. ['map', 'digit']
  46. @param Map instance of render.Map
  47. @param auimgs AUI manager
  48. @param kwargs wx.Frame attribures
  49. """
  50. SingleMapFrame.__init__(self, parent = parent, giface = giface, title = title,
  51. Map = Map, auimgr = auimgr, name = name, **kwargs)
  52. self._giface = giface
  53. # properties are shared in other objects, so defining here
  54. self.mapWindowProperties = MapWindowProperties()
  55. self.mapWindowProperties.setValuesFromUserSettings()
  56. self.mapWindowProperties.alignExtent = True
  57. #
  58. # Add toolbars
  59. #
  60. for toolb in toolbars:
  61. self.AddToolbar(toolb)
  62. self.activemap = self.toolbars['gcpdisp'].togglemap
  63. self.activemap.SetSelection(0)
  64. self.SrcMap = self.grwiz.SrcMap # instance of render.Map
  65. self.TgtMap = self.grwiz.TgtMap # instance of render.Map
  66. self._mgr.SetDockSizeConstraint(0.5, 0.5)
  67. #
  68. # Add statusbar
  69. #
  70. # items for choice
  71. self.statusbarItems = [sb.SbCoordinates,
  72. sb.SbRegionExtent,
  73. sb.SbCompRegionExtent,
  74. sb.SbShowRegion,
  75. sb.SbResolution,
  76. sb.SbDisplayGeometry,
  77. sb.SbMapScale,
  78. sb.SbProjection,
  79. sbgcp.SbGoToGCP,
  80. sbgcp.SbRMSError]
  81. # create statusbar and its manager
  82. statusbar = self.CreateStatusBar(number = 4, style = 0)
  83. statusbar.SetStatusWidths([-5, -2, -1, -1])
  84. self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar)
  85. # fill statusbar manager
  86. self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar)
  87. self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2))
  88. self.statusbarManager.AddStatusbarItem(sb.SbRender(self, statusbar = statusbar, position = 3))
  89. self.statusbarManager.SetMode(8) # goto GCP
  90. #
  91. # Init map display (buffered DC & set default cursor)
  92. #
  93. self.grwiz.SwitchEnv('source')
  94. self.SrcMapWindow = BufferedMapWindow(parent=self, giface=self._giface, id=wx.ID_ANY,
  95. properties=self.mapWindowProperties,
  96. Map=self.SrcMap)
  97. self.grwiz.SwitchEnv('target')
  98. self.TgtMapWindow = BufferedMapWindow(parent=self, giface=self._giface, id=wx.ID_ANY,
  99. properties=self.mapWindowProperties,
  100. Map=self.TgtMap)
  101. self.MapWindow = self.SrcMapWindow
  102. self.Map = self.SrcMap
  103. self._setUpMapWindow(self.SrcMapWindow)
  104. self._setUpMapWindow(self.TgtMapWindow)
  105. self.SrcMapWindow.SetNamedCursor('cross')
  106. self.TgtMapWindow.SetNamedCursor('cross')
  107. # used to switch current map (combo box in toolbar)
  108. self.SrcMapWindow.mouseEntered.connect(
  109. lambda:
  110. self._setActiveMapWindow(self.SrcMapWindow))
  111. self.TgtMapWindow.mouseEntered.connect(
  112. lambda:
  113. self._setActiveMapWindow(self.TgtMapWindow))
  114. #
  115. # initialize region values
  116. #
  117. self._initMap(Map = self.SrcMap)
  118. self._initMap(Map = self.TgtMap)
  119. self.GetMapToolbar().SelectDefault()
  120. #
  121. # Bind various events
  122. #
  123. self.activemap.Bind(wx.EVT_CHOICE, self.OnUpdateActive)
  124. self.Bind(wx.EVT_SIZE, self.OnSize)
  125. #
  126. # Update fancy gui style
  127. #
  128. # AuiManager wants a CentrePane, workaround to get two equally sized windows
  129. self.list = self.CreateGCPList()
  130. #self.SrcMapWindow.SetSize((300, 300))
  131. #self.TgtMapWindow.SetSize((300, 300))
  132. self.list.SetSize((100, 150))
  133. self._mgr.AddPane(self.list, wx.aui.AuiPaneInfo().
  134. Name("gcplist").Caption(_("GCP List")).LeftDockable(False).
  135. RightDockable(False).PinButton().FloatingSize((600,200)).
  136. CloseButton(False).DestroyOnClose(True).
  137. Top().Layer(1).MinSize((200,100)))
  138. self._mgr.AddPane(self.SrcMapWindow, wx.aui.AuiPaneInfo().
  139. Name("source").Caption(_("Source Display")).Dockable(False).
  140. CloseButton(False).DestroyOnClose(True).Floatable(False).
  141. Centre())
  142. self._mgr.AddPane(self.TgtMapWindow, wx.aui.AuiPaneInfo().
  143. Name("target").Caption(_("Target Display")).Dockable(False).
  144. CloseButton(False).DestroyOnClose(True).Floatable(False).
  145. Right().Layer(0))
  146. srcwidth, srcheight = self.SrcMapWindow.GetSize()
  147. tgtwidth, tgtheight = self.TgtMapWindow.GetSize()
  148. srcwidth = (srcwidth + tgtwidth) / 2
  149. self._mgr.GetPane("target").Hide()
  150. self._mgr.Update()
  151. self._mgr.GetPane("source").BestSize((srcwidth, srcheight))
  152. self._mgr.GetPane("target").BestSize((srcwidth, srcheight))
  153. if self.show_target:
  154. self._mgr.GetPane("target").Show()
  155. else:
  156. self.activemap.Enable(False)
  157. # needed by Mac OS, does not harm on Linux, breaks display on Windows
  158. if platform.system() != 'Windows':
  159. self._mgr.Update()
  160. #
  161. # Init print module and classes
  162. #
  163. self.printopt = PrintOptions(self, self.MapWindow)
  164. #
  165. # Initialization of digitization tool
  166. #
  167. self.digit = None
  168. # set active map
  169. self.MapWindow = self.SrcMapWindow
  170. self.Map = self.SrcMap
  171. # do not init zoom history here, that happens when zooming to map(s)
  172. #
  173. # Re-use dialogs
  174. #
  175. self.dialogs = {}
  176. self.dialogs['attributes'] = None
  177. self.dialogs['category'] = None
  178. self.dialogs['barscale'] = None
  179. self.dialogs['legend'] = None
  180. self.decorationDialog = None # decoration/overlays
  181. # doing nice things in statusbar when other things are ready
  182. self.statusbarManager.Update()
  183. def _setUpMapWindow(self, mapWindow):
  184. # TODO: almost the smae implementation as for MapFrameBase (only names differ)
  185. # enable or disable zoom history tool
  186. mapWindow.zoomHistoryAvailable.connect(
  187. lambda:
  188. self.GetMapToolbar().Enable('zoomback', enable=True))
  189. mapWindow.zoomHistoryUnavailable.connect(
  190. lambda:
  191. self.GetMapToolbar().Enable('zoomback', enable=False))
  192. mapWindow.mouseMoving.connect(self.CoordinatesChanged)
  193. def AddToolbar(self, name):
  194. """!Add defined toolbar to the window
  195. Currently known toolbars are:
  196. - 'map' - basic map toolbar
  197. - 'vdigit' - vector digitizer
  198. - 'gcpdisp' - GCP Manager, Display
  199. - 'gcpman' - GCP Manager, points management
  200. - 'nviz' - 3D view mode
  201. """
  202. # default toolbar
  203. if name == "map":
  204. self.toolbars['map'] = MapToolbar(self, self._toolSwitcher)
  205. self._mgr.AddPane(self.toolbars['map'],
  206. wx.aui.AuiPaneInfo().
  207. Name("maptoolbar").Caption(_("Map Toolbar")).
  208. ToolbarPane().Top().
  209. LeftDockable(False).RightDockable(False).
  210. BottomDockable(False).TopDockable(True).
  211. CloseButton(False).Layer(2).
  212. BestSize((self.toolbars['map'].GetSize())))
  213. # GCP display
  214. elif name == "gcpdisp":
  215. self.toolbars['gcpdisp'] = GCPDisplayToolbar(self, self._toolSwitcher)
  216. self._mgr.AddPane(self.toolbars['gcpdisp'],
  217. wx.aui.AuiPaneInfo().
  218. Name("gcpdisplaytoolbar").Caption(_("GCP Display toolbar")).
  219. ToolbarPane().Top().
  220. LeftDockable(False).RightDockable(False).
  221. BottomDockable(False).TopDockable(True).
  222. CloseButton(False).Layer(2))
  223. if self.show_target == False:
  224. self.toolbars['gcpdisp'].Enable('zoommenu', enable = False)
  225. self.toolbars['gcpman'] = GCPManToolbar(self)
  226. self._mgr.AddPane(self.toolbars['gcpman'],
  227. wx.aui.AuiPaneInfo().
  228. Name("gcpmanagertoolbar").Caption(_("GCP Manager toolbar")).
  229. ToolbarPane().Top().Row(1).
  230. LeftDockable(False).RightDockable(False).
  231. BottomDockable(False).TopDockable(True).
  232. CloseButton(False).Layer(2))
  233. self._mgr.Update()
  234. def OnUpdateProgress(self, event):
  235. """
  236. Update progress bar info
  237. """
  238. self.GetProgressBar().UpdateProgress(event.layer, event.map)
  239. event.Skip()
  240. def OnFocus(self, event):
  241. """
  242. Change choicebook page to match display.
  243. Or set display for georectifying
  244. """
  245. # was in if layer manager but considering the state it was executed
  246. # always, moreover, there is no layer manager dependent code
  247. # in GCP Management, set focus to current MapWindow for mouse actions
  248. self.OnPointer(event)
  249. self.MapWindow.SetFocus()
  250. event.Skip()
  251. def OnDraw(self, event):
  252. """!Re-display current map composition
  253. """
  254. self.MapWindow.UpdateMap(render = False)
  255. def OnRender(self, event):
  256. """!Re-render map composition (each map layer)
  257. """
  258. # FIXME: remove qlayer code or use RemoveQueryLayer() now in mapdisp.frame
  259. # delete tmp map layers (queries)
  260. qlayer = self.Map.GetListOfLayers(name=globalvar.QUERYLAYER)
  261. for layer in qlayer:
  262. self.Map.DeleteLayer(layer)
  263. self.SrcMapWindow.UpdateMap(render=True)
  264. if self.show_target:
  265. self.TgtMapWindow.UpdateMap(render=True)
  266. # update statusbar
  267. self.StatusbarUpdate()
  268. def OnPointer(self, event):
  269. """!Pointer button clicked
  270. """
  271. # change the cursor
  272. self.SrcMapWindow.SetNamedCursor('cross')
  273. self.SrcMapWindow.mouse['use'] = "pointer"
  274. self.SrcMapWindow.mouse['box'] = "point"
  275. self.TgtMapWindow.SetNamedCursor('cross')
  276. self.TgtMapWindow.mouse['use'] = "pointer"
  277. self.TgtMapWindow.mouse['box'] = "point"
  278. def OnZoomIn(self, event):
  279. """
  280. Zoom in the map.
  281. Set mouse cursor, zoombox attributes, and zoom direction
  282. """
  283. self.MapWindow.mouse['use'] = "zoom"
  284. self.MapWindow.mouse['box'] = "box"
  285. self.MapWindow.zoomtype = 1
  286. self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
  287. # change the cursor
  288. self.MapWindow.SetNamedCursor('cross')
  289. if self.MapWindow == self.SrcMapWindow:
  290. win = self.TgtMapWindow
  291. elif self.MapWindow == self.TgtMapWindow:
  292. win = self.SrcMapWindow
  293. win.mouse['use'] = "zoom"
  294. win.mouse['box'] = "box"
  295. win.zoomtype = 1
  296. win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
  297. # change the cursor
  298. win.SetNamedCursor('cross')
  299. def OnZoomOut(self, event):
  300. """
  301. Zoom out the map.
  302. Set mouse cursor, zoombox attributes, and zoom direction
  303. """
  304. self.MapWindow.mouse['use'] = "zoom"
  305. self.MapWindow.mouse['box'] = "box"
  306. self.MapWindow.zoomtype = -1
  307. self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
  308. # change the cursor
  309. self.MapWindow.SetNamedCursor('cross')
  310. if self.MapWindow == self.SrcMapWindow:
  311. win = self.TgtMapWindow
  312. elif self.MapWindow == self.TgtMapWindow:
  313. win = self.SrcMapWindow
  314. win.mouse['use'] = "zoom"
  315. win.mouse['box'] = "box"
  316. win.zoomtype = -1
  317. win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
  318. # change the cursor
  319. win.SetNamedCursor('cross')
  320. def OnPan(self, event):
  321. """
  322. Panning, set mouse to drag
  323. """
  324. self.MapWindow.mouse['use'] = "pan"
  325. self.MapWindow.mouse['box'] = "pan"
  326. self.MapWindow.zoomtype = 0
  327. # change the cursor
  328. self.MapWindow.SetNamedCursor('hand')
  329. if self.MapWindow == self.SrcMapWindow:
  330. win = self.TgtMapWindow
  331. elif self.MapWindow == self.TgtMapWindow:
  332. win = self.SrcMapWindow
  333. win.mouse['use'] = "pan"
  334. win.mouse['box'] = "pan"
  335. win.zoomtype = 0
  336. # change the cursor
  337. win.SetNamedCursor('hand')
  338. def OnErase(self, event):
  339. """
  340. Erase the canvas
  341. """
  342. self.MapWindow.EraseMap()
  343. if self.MapWindow == self.SrcMapWindow:
  344. win = self.TgtMapWindow
  345. elif self.MapWindow == self.TgtMapWindow:
  346. win = self.SrcMapWindow
  347. win.EraseMap()
  348. def SaveToFile(self, event):
  349. """!Save map to image
  350. """
  351. img = self.MapWindow.img
  352. if not img:
  353. GMessage(parent = self,
  354. message = _("Nothing to render (empty map). Operation canceled."))
  355. return
  356. filetype, ltype = GetImageHandlers(img)
  357. # get size
  358. dlg = ImageSizeDialog(self)
  359. dlg.CentreOnParent()
  360. if dlg.ShowModal() != wx.ID_OK:
  361. dlg.Destroy()
  362. return
  363. width, height = dlg.GetValues()
  364. dlg.Destroy()
  365. # get filename
  366. dlg = wx.FileDialog(parent = self,
  367. message = _("Choose a file name to save the image "
  368. "(no need to add extension)"),
  369. wildcard = filetype,
  370. style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  371. if dlg.ShowModal() == wx.ID_OK:
  372. path = dlg.GetPath()
  373. if not path:
  374. dlg.Destroy()
  375. return
  376. base, ext = os.path.splitext(path)
  377. fileType = ltype[dlg.GetFilterIndex()]['type']
  378. extType = ltype[dlg.GetFilterIndex()]['ext']
  379. if ext != extType:
  380. path = base + '.' + extType
  381. self.MapWindow.SaveToFile(path, fileType,
  382. width, height)
  383. dlg.Destroy()
  384. def PrintMenu(self, event):
  385. """
  386. Print options and output menu for map display
  387. """
  388. point = wx.GetMousePosition()
  389. printmenu = wx.Menu()
  390. # Add items to the menu
  391. setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup'))
  392. printmenu.AppendItem(setup)
  393. self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
  394. preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview'))
  395. printmenu.AppendItem(preview)
  396. self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
  397. doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display'))
  398. printmenu.AppendItem(doprint)
  399. self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
  400. # Popup the menu. If an item is selected then its handler
  401. # will be called before PopupMenu returns.
  402. self.PopupMenu(printmenu)
  403. printmenu.Destroy()
  404. def OnZoomToRaster(self, event):
  405. """!
  406. Set display extents to match selected raster map (ignore NULLs)
  407. """
  408. self.MapWindow.ZoomToMap(ignoreNulls = True)
  409. def OnZoomToSaved(self, event):
  410. """!Set display geometry to match extents in
  411. saved region file
  412. """
  413. self.MapWindow.ZoomToSaved()
  414. def OnDisplayToWind(self, event):
  415. """!Set computational region (WIND file) to match display
  416. extents
  417. """
  418. self.MapWindow.DisplayToWind()
  419. def SaveDisplayRegion(self, event):
  420. """!Save display extents to named region file.
  421. """
  422. self.MapWindow.SaveDisplayRegion()
  423. def OnZoomMenu(self, event):
  424. """!Popup Zoom menu
  425. """
  426. point = wx.GetMousePosition()
  427. zoommenu = wx.Menu()
  428. # Add items to the menu
  429. zoomwind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to computational region (set with g.region)'))
  430. zoommenu.AppendItem(zoomwind)
  431. self.Bind(wx.EVT_MENU, self.OnZoomToWind, zoomwind)
  432. zoomdefault = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to default region'))
  433. zoommenu.AppendItem(zoomdefault)
  434. self.Bind(wx.EVT_MENU, self.OnZoomToDefault, zoomdefault)
  435. zoomsaved = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to saved region'))
  436. zoommenu.AppendItem(zoomsaved)
  437. self.Bind(wx.EVT_MENU, self.OnZoomToSaved, zoomsaved)
  438. savewind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Set computational region from display'))
  439. zoommenu.AppendItem(savewind)
  440. self.Bind(wx.EVT_MENU, self.OnDisplayToWind, savewind)
  441. savezoom = wx.MenuItem(zoommenu, wx.ID_ANY, _('Save display geometry to named region'))
  442. zoommenu.AppendItem(savezoom)
  443. self.Bind(wx.EVT_MENU, self.SaveDisplayRegion, savezoom)
  444. # Popup the menu. If an item is selected then its handler
  445. # will be called before PopupMenu returns.
  446. self.PopupMenu(zoommenu)
  447. zoommenu.Destroy()
  448. def IsStandalone(self):
  449. """!Check if Map display is standalone"""
  450. # we do not know and we do not care, so always False
  451. return True
  452. def GetLayerManager(self):
  453. """!Get reference to Layer Manager
  454. @return always None
  455. """
  456. return None
  457. def GetSrcWindow(self):
  458. return self.SrcMapWindow
  459. def GetTgtWindow(self):
  460. return self.TgtMapWindow
  461. def GetShowTarget(self):
  462. return self.show_target
  463. def GetMapToolbar(self):
  464. """!Returns toolbar with zooming tools"""
  465. return self.toolbars['gcpdisp']
  466. def _setActiveMapWindow(self, mapWindow):
  467. if not self.MapWindow == mapWindow:
  468. self.MapWindow = mapWindow
  469. self.Map = mapWindow.Map
  470. self.UpdateActive(mapWindow)
  471. # needed for wingrass
  472. self.SetFocus()