ip2i_mapdisplay.py 17 KB

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