mapdisplay.py 18 KB

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