frame.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597
  1. """
  2. @package iclass.frame
  3. @brief wxIClass frame with toolbar for digitizing training areas and
  4. for spectral signature analysis.
  5. Classes:
  6. - frame::IClassMapFrame
  7. - frame::MapManager
  8. (C) 2006-2013 by the GRASS Development Team
  9. This program is free software under the GNU General Public
  10. License (>=v2). Read the file COPYING that comes with GRASS
  11. for details.
  12. @author Vaclav Petras <wenzeslaus gmail.com>
  13. @author Anna Kratochvilova <kratochanna gmail.com>
  14. """
  15. import os
  16. import six
  17. import copy
  18. import tempfile
  19. import wx
  20. from ctypes import *
  21. try:
  22. from grass.lib.imagery import *
  23. from grass.lib.vector import *
  24. haveIClass = True
  25. errMsg = ""
  26. except ImportError as e:
  27. haveIClass = False
  28. errMsg = _("Loading imagery lib failed.\n%s") % e
  29. import grass.script as grass
  30. from mapdisp import statusbar as sb
  31. from mapdisp.main import StandaloneMapDisplayGrassInterface
  32. from mapwin.buffered import BufferedMapWindow
  33. from vdigit.toolbars import VDigitToolbar
  34. from gui_core.mapdisp import DoubleMapFrame
  35. from core.render import Map
  36. from core.gcmd import RunCommand, GMessage, GError
  37. from gui_core.dialogs import SetOpacityDialog
  38. from gui_core.wrap import Menu
  39. from mapwin.base import MapWindowProperties
  40. from dbmgr.vinfo import VectorDBInfo
  41. from iclass.digit import IClassVDigitWindow, IClassVDigit
  42. from iclass.toolbars import (
  43. IClassMapToolbar,
  44. IClassMiscToolbar,
  45. IClassToolbar,
  46. IClassMapManagerToolbar,
  47. )
  48. from iclass.statistics import StatisticsData
  49. from iclass.dialogs import (
  50. IClassCategoryManagerDialog,
  51. IClassGroupDialog,
  52. IClassSignatureFileDialog,
  53. IClassExportAreasDialog,
  54. IClassMapDialog,
  55. )
  56. from iclass.plots import PlotPanel
  57. from grass.pydispatch.signal import Signal
  58. class IClassMapFrame(DoubleMapFrame):
  59. """wxIClass main frame
  60. It has two map windows one for digitizing training areas and one for
  61. result preview.
  62. It generates histograms, raster maps and signature files using
  63. @c I_iclass_* functions from C imagery library.
  64. It is wxGUI counterpart of old i.class module.
  65. """
  66. def __init__(
  67. self,
  68. parent=None,
  69. giface=None,
  70. title=_("Supervised Classification Tool"),
  71. toolbars=["iClassMisc", "iClassMap", "vdigit", "iClass"],
  72. size=(875, 600),
  73. name="IClassWindow",
  74. **kwargs,
  75. ):
  76. """
  77. :param parent: (no parent is expected)
  78. :param title: window title
  79. :param toolbars: dictionary of active toolbars (defalult value represents all toolbars)
  80. :param size: default size
  81. """
  82. DoubleMapFrame.__init__(
  83. self,
  84. parent=parent,
  85. title=title,
  86. name=name,
  87. firstMap=Map(),
  88. secondMap=Map(),
  89. **kwargs,
  90. )
  91. if giface:
  92. self.giface = giface
  93. else:
  94. self.giface = StandaloneMapDisplayGrassInterface(self)
  95. self.tree = None
  96. self.mapWindowProperties = MapWindowProperties()
  97. self.mapWindowProperties.setValuesFromUserSettings()
  98. # show computation region by defaut
  99. self.mapWindowProperties.showRegion = True
  100. self.firstMapWindow = IClassVDigitWindow(
  101. parent=self,
  102. giface=self.giface,
  103. properties=self.mapWindowProperties,
  104. map=self.firstMap,
  105. )
  106. self.secondMapWindow = BufferedMapWindow(
  107. parent=self,
  108. giface=self.giface,
  109. properties=self.mapWindowProperties,
  110. Map=self.secondMap,
  111. )
  112. self.MapWindow = self.firstMapWindow # current by default
  113. self._bindWindowsActivation()
  114. self._setUpMapWindow(self.firstMapWindow)
  115. self._setUpMapWindow(self.secondMapWindow)
  116. self.firstMapWindow.InitZoomHistory()
  117. self.secondMapWindow.InitZoomHistory()
  118. # TODO: for vdigit: it does nothing here because areas do not produce
  119. # this info
  120. self.firstMapWindow.digitizingInfo.connect(
  121. lambda text: self.statusbarManager.statusbarItems[
  122. "coordinates"
  123. ].SetAdditionalInfo(text)
  124. )
  125. self.firstMapWindow.digitizingInfoUnavailable.connect(
  126. lambda: self.statusbarManager.statusbarItems[
  127. "coordinates"
  128. ].SetAdditionalInfo(None)
  129. )
  130. self.SetSize(size)
  131. #
  132. # Signals
  133. #
  134. self.groupSet = Signal("IClassMapFrame.groupSet")
  135. self.categoryChanged = Signal("IClassMapFrame.categoryChanged")
  136. self.InitStatistics()
  137. #
  138. # Add toolbars
  139. #
  140. for toolb in toolbars:
  141. self.AddToolbar(toolb)
  142. self.firstMapWindow.SetToolbar(self.toolbars["vdigit"])
  143. self.GetMapToolbar().GetActiveMapTool().Bind(wx.EVT_CHOICE, self.OnUpdateActive)
  144. #
  145. # Add statusbar
  146. #
  147. # items for choice
  148. self.statusbarItems = [
  149. sb.SbCoordinates,
  150. sb.SbRegionExtent,
  151. sb.SbCompRegionExtent,
  152. sb.SbShowRegion,
  153. sb.SbAlignExtent,
  154. sb.SbResolution,
  155. sb.SbDisplayGeometry,
  156. sb.SbMapScale,
  157. sb.SbGoTo,
  158. sb.SbProjection,
  159. ]
  160. # create statusbar and its manager
  161. statusbar = self.CreateStatusBar(number=4, style=0)
  162. statusbar.SetStatusWidths([-5, -2, -1, -1])
  163. self.statusbarManager = sb.SbManager(mapframe=self, statusbar=statusbar)
  164. # fill statusbar manager
  165. self.statusbarManager.AddStatusbarItemsByClass(
  166. self.statusbarItems, mapframe=self, statusbar=statusbar
  167. )
  168. self.statusbarManager.AddStatusbarItem(
  169. sb.SbMask(self, statusbar=statusbar, position=2)
  170. )
  171. self.statusbarManager.AddStatusbarItem(
  172. sb.SbRender(self, statusbar=statusbar, position=3)
  173. )
  174. self.statusbarManager.Update()
  175. self.trainingMapManager = MapManager(
  176. self, mapWindow=self.GetFirstWindow(), Map=self.GetFirstMap()
  177. )
  178. self.previewMapManager = MapManager(
  179. self, mapWindow=self.GetSecondWindow(), Map=self.GetSecondMap()
  180. )
  181. self.changes = False
  182. self.exportVector = None
  183. # dialogs
  184. self.dialogs = dict()
  185. self.dialogs["classManager"] = None
  186. self.dialogs["scatt_plot"] = None
  187. # just to make digitizer happy
  188. self.dialogs["attributes"] = None
  189. self.dialogs["category"] = None
  190. # PyPlot init
  191. self.plotPanel = PlotPanel(self, giface=self.giface, stats_data=self.stats_data)
  192. self._addPanes()
  193. self._mgr.Update()
  194. self.trainingMapManager.SetToolbar(self.toolbars["iClassTrainingMapManager"])
  195. self.previewMapManager.SetToolbar(self.toolbars["iClassPreviewMapManager"])
  196. # default action
  197. self.GetMapToolbar().SelectDefault()
  198. wx.CallAfter(self.AddTrainingAreaMap)
  199. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  200. self.Bind(wx.EVT_SIZE, self.OnSize)
  201. self.SendSizeEvent()
  202. def OnCloseWindow(self, event):
  203. self.GetFirstWindow().GetDigit().CloseMap()
  204. self.plotPanel.CloseWindow()
  205. self._cleanup()
  206. self._mgr.UnInit()
  207. self.Destroy()
  208. def _cleanup(self):
  209. """Frees C structs and removes vector map and all raster maps."""
  210. I_free_signatures(self.signatures)
  211. I_free_group_ref(self.refer)
  212. for st in self.cStatisticsDict.values():
  213. I_iclass_free_statistics(st)
  214. self.RemoveTempVector()
  215. for i in self.stats_data.GetCategories():
  216. self.RemoveTempRaster(self.stats_data.GetStatistics(i).rasterName)
  217. def OnHelp(self, event):
  218. """Show help page"""
  219. self.giface.Help(entry="wxGUI.iclass")
  220. def _getTempVectorName(self):
  221. """Return new name for temporary vector map (training areas)"""
  222. vectorPath = grass.tempfile(create=False)
  223. return "trAreas" + os.path.basename(vectorPath).replace(".", "")
  224. def SetGroup(self, group, subgroup):
  225. """Set group and subgroup manually"""
  226. self.g = {"group": group, "subgroup": subgroup}
  227. def CreateTempVector(self):
  228. """Create temporary vector map for training areas"""
  229. vectorName = self._getTempVectorName()
  230. env = os.environ.copy()
  231. env["GRASS_VECTOR_TEMPORARY"] = "1" # create temporary map
  232. cmd = ("v.edit", {"tool": "create", "map": vectorName})
  233. ret = RunCommand(prog=cmd[0], parent=self, env=env, **cmd[1])
  234. if ret != 0:
  235. return False
  236. return vectorName
  237. def RemoveTempVector(self):
  238. """Removes temporary vector map with training areas"""
  239. ret = RunCommand(
  240. prog="g.remove",
  241. parent=self,
  242. flags="f",
  243. type="vector",
  244. name=self.trainingAreaVector,
  245. )
  246. if ret != 0:
  247. return False
  248. return True
  249. def RemoveTempRaster(self, raster):
  250. """Removes temporary raster maps"""
  251. self.GetFirstMap().Clean()
  252. self.GetSecondMap().Clean()
  253. ret = RunCommand(
  254. prog="g.remove", parent=self, flags="f", type="raster", name=raster
  255. )
  256. if ret != 0:
  257. return False
  258. return True
  259. def AddToolbar(self, name):
  260. """Add defined toolbar to the window
  261. Currently known toolbars are:
  262. - 'iClassMap' - basic map toolbar
  263. - 'iClass' - iclass tools
  264. - 'iClassMisc' - miscellaneous (help)
  265. - 'vdigit' - digitizer toolbar (areas)
  266. Toolbars 'iClassPreviewMapManager' are added in _addPanes().
  267. """
  268. if name == "iClassMap":
  269. self.toolbars[name] = IClassMapToolbar(self, self._toolSwitcher)
  270. self._mgr.AddPane(
  271. self.toolbars[name],
  272. wx.aui.AuiPaneInfo()
  273. .Name(name)
  274. .Caption(_("Map Toolbar"))
  275. .ToolbarPane()
  276. .Top()
  277. .LeftDockable(False)
  278. .RightDockable(False)
  279. .BottomDockable(False)
  280. .TopDockable(True)
  281. .CloseButton(False)
  282. .Layer(2)
  283. .Row(1)
  284. .Position(0)
  285. .BestSize((self.toolbars[name].GetBestSize())),
  286. )
  287. if name == "iClass":
  288. self.toolbars[name] = IClassToolbar(self, stats_data=self.stats_data)
  289. self._mgr.AddPane(
  290. self.toolbars[name],
  291. wx.aui.AuiPaneInfo()
  292. .Name(name)
  293. .Caption(_("IClass Toolbar"))
  294. .ToolbarPane()
  295. .Top()
  296. .LeftDockable(False)
  297. .RightDockable(False)
  298. .BottomDockable(False)
  299. .TopDockable(True)
  300. .CloseButton(False)
  301. .Layer(2)
  302. .Row(2)
  303. .Position(0)
  304. .BestSize((self.toolbars[name].GetBestSize())),
  305. )
  306. if name == "iClassMisc":
  307. self.toolbars[name] = IClassMiscToolbar(self)
  308. self._mgr.AddPane(
  309. self.toolbars[name],
  310. wx.aui.AuiPaneInfo()
  311. .Name(name)
  312. .Caption(_("IClass Misc Toolbar"))
  313. .ToolbarPane()
  314. .Top()
  315. .LeftDockable(False)
  316. .RightDockable(False)
  317. .BottomDockable(False)
  318. .TopDockable(True)
  319. .CloseButton(False)
  320. .Layer(2)
  321. .Row(1)
  322. .Position(1)
  323. .BestSize((self.toolbars[name].GetBestSize())),
  324. )
  325. if name == "vdigit":
  326. self.toolbars[name] = VDigitToolbar(
  327. parent=self,
  328. toolSwitcher=self._toolSwitcher,
  329. MapWindow=self.GetFirstWindow(),
  330. digitClass=IClassVDigit,
  331. giface=self.giface,
  332. tools=[
  333. "addArea",
  334. "moveVertex",
  335. "addVertex",
  336. "removeVertex",
  337. "editLine",
  338. "moveLine",
  339. "deleteArea",
  340. "undo",
  341. "redo",
  342. "settings",
  343. ],
  344. )
  345. self._mgr.AddPane(
  346. self.toolbars[name],
  347. wx.aui.AuiPaneInfo()
  348. .Name(name)
  349. .Caption(_("Digitization Toolbar"))
  350. .ToolbarPane()
  351. .Top()
  352. .LeftDockable(False)
  353. .RightDockable(False)
  354. .BottomDockable(False)
  355. .TopDockable(True)
  356. .CloseButton(False)
  357. .Layer(2)
  358. .Row(2)
  359. .Position(1)
  360. .BestSize((self.toolbars[name].GetBestSize())),
  361. )
  362. def _addPanes(self):
  363. """Add mapwindows and toolbars to aui manager"""
  364. self._addPaneMapWindow(name="training", position=0)
  365. self._addPaneToolbar(name="iClassTrainingMapManager", position=1)
  366. self._addPaneMapWindow(name="preview", position=2)
  367. self._addPaneToolbar(name="iClassPreviewMapManager", position=3)
  368. # otherwise best size was ignored
  369. self._mgr.SetDockSizeConstraint(0.5, 0.5)
  370. self._mgr.AddPane(
  371. self.plotPanel,
  372. wx.aui.AuiPaneInfo()
  373. .Name("plots")
  374. .Caption(_("Plots"))
  375. .Dockable(False)
  376. .Floatable(False)
  377. .CloseButton(False)
  378. .Left()
  379. .Layer(1)
  380. .BestSize((335, -1)),
  381. )
  382. def _addPaneToolbar(self, name, position):
  383. if name == "iClassPreviewMapManager":
  384. parent = self.previewMapManager
  385. else:
  386. parent = self.trainingMapManager
  387. self.toolbars[name] = IClassMapManagerToolbar(self, parent)
  388. self._mgr.AddPane(
  389. self.toolbars[name],
  390. wx.aui.AuiPaneInfo()
  391. .ToolbarPane()
  392. .Movable()
  393. .Name(name)
  394. .CloseButton(False)
  395. .Center()
  396. .Layer(0)
  397. .Position(position)
  398. .BestSize((self.toolbars[name].GetBestSize())),
  399. )
  400. def _addPaneMapWindow(self, name, position):
  401. if name == "preview":
  402. window = self.GetSecondWindow()
  403. caption = _("Preview Display")
  404. else:
  405. window = self.GetFirstWindow()
  406. caption = _("Training Areas Display")
  407. self._mgr.AddPane(
  408. window,
  409. wx.aui.AuiPaneInfo()
  410. .Name(name)
  411. .Caption(caption)
  412. .Dockable(False)
  413. .Floatable(False)
  414. .CloseButton(False)
  415. .Center()
  416. .Layer(0)
  417. .Position(position),
  418. )
  419. def OnUpdateActive(self, event):
  420. """
  421. .. todo::
  422. move to DoubleMapFrame?
  423. """
  424. if self.GetMapToolbar().GetActiveMap() == 0:
  425. self.MapWindow = self.firstMapWindow
  426. self.Map = self.firstMap
  427. else:
  428. self.MapWindow = self.secondMapWindow
  429. self.Map = self.secondMap
  430. self.UpdateActive(self.MapWindow)
  431. # for wingrass
  432. if os.name == "nt":
  433. self.MapWindow.SetFocus()
  434. def UpdateActive(self, win):
  435. """
  436. .. todo::
  437. move to DoubleMapFrame?
  438. """
  439. mapTb = self.GetMapToolbar()
  440. # optionally disable tool zoomback tool
  441. mapTb.Enable("zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1))
  442. if mapTb.GetActiveMap() != (win == self.secondMapWindow):
  443. mapTb.SetActiveMap((win == self.secondMapWindow))
  444. self.StatusbarUpdate()
  445. def ActivateFirstMap(self, event=None):
  446. DoubleMapFrame.ActivateFirstMap(self, event)
  447. self.GetMapToolbar().Enable(
  448. "zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1)
  449. )
  450. def ActivateSecondMap(self, event=None):
  451. DoubleMapFrame.ActivateSecondMap(self, event)
  452. self.GetMapToolbar().Enable(
  453. "zoomBack", enable=(len(self.MapWindow.zoomhistory) > 1)
  454. )
  455. def GetMapToolbar(self):
  456. """Returns toolbar with zooming tools"""
  457. return self.toolbars["iClassMap"] if "iClassMap" in self.toolbars else None
  458. def GetClassColor(self, cat):
  459. """Get class color as string
  460. :param cat: class category
  461. :return: 'R:G:B'
  462. """
  463. if cat in self.stats_data.GetCategories():
  464. return self.stats_data.GetStatistics(cat).color
  465. return "0:0:0"
  466. def OnZoomMenu(self, event):
  467. """Popup Zoom menu"""
  468. zoommenu = Menu()
  469. # Add items to the menu
  470. i = 0
  471. for label, handler in (
  472. (
  473. _("Adjust Training Area Display to Preview Display"),
  474. self.OnZoomToPreview,
  475. ),
  476. (
  477. _("Adjust Preview display to Training Area Display"),
  478. self.OnZoomToTraining,
  479. ),
  480. (_("Display synchronization ON"), lambda event: self.SetBindRegions(True)),
  481. (
  482. _("Display synchronization OFF"),
  483. lambda event: self.SetBindRegions(False),
  484. ),
  485. ):
  486. if label is None:
  487. zoommenu.AppendSeparator()
  488. continue
  489. item = wx.MenuItem(zoommenu, wx.ID_ANY, label)
  490. zoommenu.AppendItem(item)
  491. self.Bind(wx.EVT_MENU, handler, item)
  492. if i == 3:
  493. item.Enable(not self._bindRegions)
  494. elif i == 4:
  495. item.Enable(self._bindRegions)
  496. i += 1
  497. # Popup the menu. If an item is selected then its handler
  498. # will be called before PopupMenu returns.
  499. self.PopupMenu(zoommenu)
  500. zoommenu.Destroy()
  501. def OnZoomToTraining(self, event):
  502. """Set preview display to match extents of training display"""
  503. if not self.MapWindow == self.GetSecondWindow():
  504. self.MapWindow = self.GetSecondWindow()
  505. self.Map = self.GetSecondMap()
  506. self.UpdateActive(self.GetSecondWindow())
  507. newreg = self.firstMap.GetCurrentRegion()
  508. self.GetSecondMap().region = copy.copy(newreg)
  509. self.Render(self.GetSecondWindow())
  510. def OnZoomToPreview(self, event):
  511. """Set preview display to match extents of training display"""
  512. if not self.MapWindow == self.GetFirstWindow():
  513. self.MapWindow = self.GetFirstWindow()
  514. self.Map = self.GetFirstMap()
  515. self.UpdateActive(self.GetFirstWindow())
  516. newreg = self.GetSecondMap().GetCurrentRegion()
  517. self.GetFirstMap().region = copy.copy(newreg)
  518. self.Render(self.GetFirstWindow())
  519. def AddBands(self):
  520. """Add imagery group"""
  521. dlg = IClassGroupDialog(self, group=self.g["group"])
  522. while True:
  523. if dlg.ShowModal() == wx.ID_OK:
  524. if dlg.GetGroupBandsErr(parent=self):
  525. g, s = dlg.GetData()
  526. group = grass.find_file(name=g, element="group")
  527. self.g["group"] = group["name"]
  528. self.g["subgroup"] = s
  529. self.groupSet.emit(
  530. group=self.g["group"], subgroup=self.g["subgroup"]
  531. )
  532. break
  533. else:
  534. break
  535. dlg.Destroy()
  536. def OnImportAreas(self, event):
  537. """Import training areas"""
  538. # check if we have any changes
  539. if self.GetAreasCount() or self.stats_data.GetCategories():
  540. qdlg = wx.MessageDialog(
  541. parent=self,
  542. message=_("All changes will be lost. " "Do you want to continue?"),
  543. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
  544. )
  545. if qdlg.ShowModal() == wx.ID_NO:
  546. qdlg.Destroy()
  547. return
  548. qdlg.Destroy()
  549. dlg = IClassMapDialog(self, title=_("Import vector map"), element="vector")
  550. if dlg.ShowModal() == wx.ID_OK:
  551. vName = dlg.GetMap()
  552. self.ImportAreas(vName)
  553. dlg.Destroy()
  554. def _checkImportedTopo(self, vector):
  555. """Check if imported vector map has areas
  556. :param str vector: vector map name
  557. :return: warning message (empty if topology is ok)
  558. """
  559. topo = grass.vector_info_topo(map=vector)
  560. warning = ""
  561. if topo["areas"] == 0:
  562. warning = _("No areas in vector map <%s>.\n" % vector)
  563. if topo["points"] or topo["lines"]:
  564. warning += _(
  565. "Vector map <%s> contains points or lines, "
  566. "these features are ignored." % vector
  567. )
  568. return warning
  569. def ImportAreas(self, vector):
  570. """Import training areas.
  571. If table connected, try load certain columns to class manager
  572. :param str vector: vector map name
  573. """
  574. warning = self._checkImportedTopo(vector)
  575. if warning:
  576. GMessage(parent=self, message=warning)
  577. return
  578. wx.BeginBusyCursor()
  579. wx.GetApp().Yield()
  580. # close, build, copy and open again the temporary vector
  581. digitClass = self.GetFirstWindow().GetDigit()
  582. # open vector map to be imported
  583. if digitClass.OpenMap(vector, update=False) is None:
  584. GError(parent=self, message=_("Unable to open vector map <%s>") % vector)
  585. return
  586. # copy features to the temporary map
  587. vname = self._getTempVectorName()
  588. # avoid deleting temporary map
  589. os.environ["GRASS_VECTOR_TEMPORARY"] = "1"
  590. if digitClass.CopyMap(vname, tmp=True, update=True) == -1:
  591. GError(
  592. parent=self,
  593. message=_("Unable to copy vector features from <%s>") % vector,
  594. )
  595. return
  596. del os.environ["GRASS_VECTOR_TEMPORARY"]
  597. # close map
  598. digitClass.CloseMap()
  599. # open temporary map (copy of imported map)
  600. self.poMapInfo = digitClass.OpenMap(vname, tmp=True)
  601. if self.poMapInfo is None:
  602. GError(parent=self, message=_("Unable to open temporary vector map"))
  603. return
  604. # remove temporary rasters
  605. for cat in self.stats_data.GetCategories():
  606. self.RemoveTempRaster(self.stats_data.GetStatistics(cat).rasterName)
  607. # clear current statistics
  608. self.stats_data.DeleteAllStatistics()
  609. # reset plots
  610. self.plotPanel.Reset()
  611. self.GetFirstWindow().UpdateMap(render=False, renderVector=True)
  612. self.ImportClasses(vector)
  613. # should be saved in attribute table?
  614. self.toolbars["iClass"].UpdateStddev(1.5)
  615. wx.EndBusyCursor()
  616. return True
  617. def ImportClasses(self, vector):
  618. """If imported map has table, try to import certain columns to class manager"""
  619. # check connection
  620. dbInfo = VectorDBInfo(vector)
  621. connected = len(dbInfo.layers.keys()) > 0
  622. # remove attribute table of temporary vector, we don't need it
  623. if connected:
  624. RunCommand("v.db.droptable", flags="f", map=self.trainingAreaVector)
  625. # we use first layer with table, TODO: user should choose
  626. layer = None
  627. for key in dbInfo.layers.keys():
  628. if dbInfo.GetTable(key):
  629. layer = key
  630. # get columns to check if we can use them
  631. # TODO: let user choose which columns mean what
  632. if layer is not None:
  633. columns = dbInfo.GetColumns(table=dbInfo.GetTable(layer))
  634. else:
  635. columns = []
  636. # get class manager
  637. if self.dialogs["classManager"] is None:
  638. self.dialogs["classManager"] = IClassCategoryManagerDialog(self)
  639. listCtrl = self.dialogs["classManager"].GetListCtrl()
  640. # unable to load data (no connection, table, right columns)
  641. if (
  642. not connected
  643. or layer is None
  644. or "class" not in columns
  645. or "color" not in columns
  646. ):
  647. # no table connected
  648. cats = RunCommand(
  649. "v.category",
  650. input=vector,
  651. layer=1, # set layer?
  652. # type = ['centroid', 'area'] ?
  653. option="print",
  654. read=True,
  655. )
  656. cats = map(int, cats.strip().split())
  657. cats = sorted(list(set(cats)))
  658. for cat in cats:
  659. listCtrl.AddCategory(cat=cat, name="class_%d" % cat, color="0:0:0")
  660. # connection, table and columns exists
  661. else:
  662. columns = ["cat", "class", "color"]
  663. ret = RunCommand(
  664. "v.db.select",
  665. quiet=True,
  666. parent=self,
  667. flags="c",
  668. map=vector,
  669. layer=1,
  670. columns=",".join(columns),
  671. read=True,
  672. )
  673. records = ret.strip().splitlines()
  674. for record in records:
  675. record = record.split("|")
  676. listCtrl.AddCategory(
  677. cat=int(record[0]), name=record[1], color=record[2]
  678. )
  679. def OnExportAreas(self, event):
  680. """Export training areas"""
  681. if self.GetAreasCount() == 0:
  682. GMessage(parent=self, message=_("No training areas to export."))
  683. return
  684. dlg = IClassExportAreasDialog(self, vectorName=self.exportVector)
  685. if dlg.ShowModal() == wx.ID_OK:
  686. vName = dlg.GetVectorName()
  687. self.exportVector = vName
  688. withTable = dlg.WithTable()
  689. dlg.Destroy()
  690. if self.ExportAreas(vectorName=vName, withTable=withTable):
  691. GMessage(
  692. _("%d training areas (%d classes) exported to vector map <%s>.")
  693. % (
  694. self.GetAreasCount(),
  695. len(self.stats_data.GetCategories()),
  696. self.exportVector,
  697. ),
  698. parent=self,
  699. )
  700. def ExportAreas(self, vectorName, withTable):
  701. """Export training areas to new vector map (with attribute table).
  702. :param str vectorName: name of exported vector map
  703. :param bool withTable: true if attribute table is required
  704. """
  705. wx.BeginBusyCursor()
  706. wx.GetApp().Yield()
  707. # close, build, copy and open again the temporary vector
  708. digitClass = self.GetFirstWindow().GetDigit()
  709. if "@" in vectorName:
  710. vectorName = vectorName.split("@")[0]
  711. if digitClass.CopyMap(vectorName) < 0:
  712. return False
  713. if not withTable:
  714. wx.EndBusyCursor()
  715. return False
  716. # add new table
  717. columns = [
  718. "class varchar(30)",
  719. "color varchar(11)",
  720. "n_cells integer",
  721. ]
  722. nbands = len(self.GetGroupLayers(self.g["group"], self.g["subgroup"]))
  723. for statistic, format in (
  724. ("min", "integer"),
  725. ("mean", "double precision"),
  726. ("max", "integer"),
  727. ):
  728. for i in range(nbands):
  729. # 10 characters limit?
  730. columns.append(
  731. "band%(band)d_%(stat)s %(format)s"
  732. % {"band": i + 1, "stat": statistic, "format": format}
  733. )
  734. if 0 != RunCommand(
  735. "v.db.addtable", map=vectorName, columns=columns, parent=self
  736. ):
  737. wx.EndBusyCursor()
  738. return False
  739. try:
  740. dbInfo = grass.vector_db(vectorName)[1]
  741. except KeyError:
  742. wx.EndBusyCursor()
  743. return False
  744. dbFile = tempfile.NamedTemporaryFile(mode="w", delete=False)
  745. if dbInfo["driver"] != "dbf":
  746. dbFile.write("BEGIN\n")
  747. # populate table
  748. for cat in self.stats_data.GetCategories():
  749. stat = self.stats_data.GetStatistics(cat)
  750. self._runDBUpdate(
  751. dbFile, table=dbInfo["table"], column="class", value=stat.name, cat=cat
  752. )
  753. self._runDBUpdate(
  754. dbFile, table=dbInfo["table"], column="color", value=stat.color, cat=cat
  755. )
  756. if not stat.IsReady():
  757. continue
  758. self._runDBUpdate(
  759. dbFile,
  760. table=dbInfo["table"],
  761. column="n_cells",
  762. value=stat.ncells,
  763. cat=cat,
  764. )
  765. for i in range(nbands):
  766. self._runDBUpdate(
  767. dbFile,
  768. table=dbInfo["table"],
  769. column="band%d_min" % (i + 1),
  770. value=stat.bands[i].min,
  771. cat=cat,
  772. )
  773. self._runDBUpdate(
  774. dbFile,
  775. table=dbInfo["table"],
  776. column="band%d_mean" % (i + 1),
  777. value=stat.bands[i].mean,
  778. cat=cat,
  779. )
  780. self._runDBUpdate(
  781. dbFile,
  782. table=dbInfo["table"],
  783. column="band%d_max" % (i + 1),
  784. value=stat.bands[i].max,
  785. cat=cat,
  786. )
  787. if dbInfo["driver"] != "dbf":
  788. dbFile.write("COMMIT\n")
  789. dbFile.file.close()
  790. ret = RunCommand(
  791. "db.execute",
  792. input=dbFile.name,
  793. driver=dbInfo["driver"],
  794. database=dbInfo["database"],
  795. )
  796. wx.EndBusyCursor()
  797. os.remove(dbFile.name)
  798. if ret != 0:
  799. return False
  800. return True
  801. def _runDBUpdate(self, tmpFile, table, column, value, cat):
  802. """Helper function for UPDATE statement
  803. :param tmpFile: file where to write UPDATE statements
  804. :param table: table name
  805. :param column: name of updated column
  806. :param value: new value
  807. :param cat: which category to update
  808. """
  809. if isinstance(value, (int, float)):
  810. tmpFile.write(
  811. "UPDATE %s SET %s = %d WHERE cat = %d\n" % (table, column, value, cat)
  812. )
  813. else:
  814. tmpFile.write(
  815. "UPDATE %s SET %s = '%s' WHERE cat = %d\n" % (table, column, value, cat)
  816. )
  817. def OnCategoryManager(self, event):
  818. """Show category management dialog"""
  819. if self.dialogs["classManager"] is None:
  820. dlg = IClassCategoryManagerDialog(self)
  821. dlg.CenterOnParent()
  822. dlg.Show()
  823. self.dialogs["classManager"] = dlg
  824. else:
  825. if not self.dialogs["classManager"].IsShown():
  826. self.dialogs["classManager"].Show()
  827. def CategoryChanged(self, currentCat):
  828. """Updates everything which depends on current category.
  829. Updates number of stddev, histograms, layer in preview display.
  830. """
  831. if currentCat:
  832. stat = self.stats_data.GetStatistics(currentCat)
  833. nstd = stat.nstd
  834. self.toolbars["iClass"].UpdateStddev(nstd)
  835. self.plotPanel.UpdateCategory(currentCat)
  836. self.plotPanel.OnPlotTypeSelected(None)
  837. name = stat.rasterName
  838. name = self.previewMapManager.GetAlias(name)
  839. if name:
  840. self.previewMapManager.SelectLayer(name)
  841. self.categoryChanged.emit(cat=currentCat)
  842. def DeleteAreas(self, cats):
  843. """Removes all training areas of given categories
  844. :param cats: list of categories to be deleted
  845. """
  846. self.firstMapWindow.GetDigit().DeleteAreasByCat(cats)
  847. self.firstMapWindow.UpdateMap(render=False, renderVector=True)
  848. def HighlightCategory(self, cats):
  849. """Highlight araes given by category"""
  850. self.firstMapWindow.GetDigit().GetDisplay().SetSelected(cats, layer=1)
  851. self.firstMapWindow.UpdateMap(render=False, renderVector=True)
  852. def ZoomToAreasByCat(self, cat):
  853. """Zoom to areas given by category"""
  854. n, s, w, e = self.GetFirstWindow().GetDigit().GetDisplay().GetRegionSelected()
  855. self.GetFirstMap().GetRegion(n=n, s=s, w=w, e=e, update=True)
  856. self.GetFirstMap().AdjustRegion()
  857. self.GetFirstMap().AlignExtentFromDisplay()
  858. self.GetFirstWindow().UpdateMap(render=True, renderVector=True)
  859. def UpdateRasterName(self, newName, cat):
  860. """Update alias of raster map when category name is changed"""
  861. origName = self.stats_data.GetStatistics(cat).rasterName
  862. self.previewMapManager.SetAlias(origName, self._addSuffix(newName))
  863. def StddevChanged(self, cat, nstd):
  864. """Standard deviation multiplier changed, rerender map, histograms"""
  865. stat = self.stats_data.GetStatistics(cat)
  866. stat.SetStatistics({"nstd": nstd})
  867. if not stat.IsReady():
  868. return
  869. raster = stat.rasterName
  870. cstat = self.cStatisticsDict[cat]
  871. I_iclass_statistics_set_nstd(cstat, nstd)
  872. I_iclass_create_raster(cstat, self.refer, raster)
  873. self.Render(self.GetSecondWindow())
  874. stat.SetBandStatistics(cstat)
  875. self.plotPanel.StddevChanged()
  876. def UpdateChangeState(self, changes):
  877. """Informs if any important changes happened
  878. since last analysis computation.
  879. """
  880. self.changes = changes
  881. def AddRasterMap(self, name, firstMap=True, secondMap=True):
  882. """Add raster map to Map"""
  883. cmdlist = ["d.rast", "map=%s" % name]
  884. if firstMap:
  885. self.GetFirstMap().AddLayer(
  886. ltype="raster",
  887. command=cmdlist,
  888. active=True,
  889. name=name,
  890. hidden=False,
  891. opacity=1.0,
  892. render=False,
  893. )
  894. self.Render(self.GetFirstWindow())
  895. if secondMap:
  896. self.GetSecondMap().AddLayer(
  897. ltype="raster",
  898. command=cmdlist,
  899. active=True,
  900. name=name,
  901. hidden=False,
  902. opacity=1.0,
  903. render=False,
  904. )
  905. self.Render(self.GetSecondWindow())
  906. def AddTrainingAreaMap(self):
  907. """Add vector map with training areas to Map (training
  908. sub-display)"""
  909. vname = self.CreateTempVector()
  910. if vname:
  911. self.trainingAreaVector = vname
  912. else:
  913. GMessage(parent=self, message=_("Failed to create temporary vector map."))
  914. return
  915. # use 'hidden' for temporary maps (TODO: do it better)
  916. mapLayer = self.GetFirstMap().AddLayer(
  917. ltype="vector",
  918. command=["d.vect", "map=%s" % vname],
  919. name=vname,
  920. active=False,
  921. hidden=True,
  922. )
  923. self.toolbars["vdigit"].StartEditing(mapLayer)
  924. self.poMapInfo = self.GetFirstWindow().GetDigit().GetMapInfo()
  925. self.Render(self.GetFirstWindow())
  926. def OnRunAnalysis(self, event):
  927. """Run analysis and update plots"""
  928. if self.RunAnalysis():
  929. currentCat = self.GetCurrentCategoryIdx()
  930. self.plotPanel.UpdatePlots(
  931. group=self.g["group"],
  932. subgroup=self.g["subgroup"],
  933. currentCat=currentCat,
  934. stats_data=self.stats_data,
  935. )
  936. def RunAnalysis(self):
  937. """Run analysis
  938. Calls C functions to compute all statistics and creates raster maps.
  939. Signatures are created but signature file is not.
  940. """
  941. if not self.CheckInput(group=self.g["group"], vector=self.trainingAreaVector):
  942. return
  943. for statistic in self.cStatisticsDict.values():
  944. I_iclass_free_statistics(statistic)
  945. self.cStatisticsDict = {}
  946. # init Ref struct with the files in group */
  947. I_free_group_ref(self.refer)
  948. if not I_iclass_init_group(self.g["group"], self.g["subgroup"], self.refer):
  949. return False
  950. I_free_signatures(self.signatures)
  951. I_iclass_init_signatures(self.signatures, self.refer)
  952. # why create copy
  953. # cats = self.statisticsList[:]
  954. cats = self.stats_data.GetCategories()
  955. for i in cats:
  956. stats = self.stats_data.GetStatistics(i)
  957. statistics_obj = IClass_statistics()
  958. statistics = pointer(statistics_obj)
  959. I_iclass_init_statistics(
  960. statistics, stats.category, stats.name, stats.color, stats.nstd
  961. )
  962. ret = I_iclass_analysis(
  963. statistics,
  964. self.refer,
  965. self.poMapInfo,
  966. "1",
  967. self.g["group"],
  968. stats.rasterName,
  969. )
  970. if ret > 0:
  971. # tests
  972. self.cStatisticsDict[i] = statistics
  973. stats.SetFromcStatistics(statistics)
  974. stats.SetReady()
  975. # stat is already part of stats_data?
  976. # self.statisticsDict[stats.category] = stats
  977. self.ConvertToNull(name=stats.rasterName)
  978. self.previewMapManager.AddLayer(
  979. name=stats.rasterName,
  980. alias=self._addSuffix(stats.name),
  981. resultsLayer=True,
  982. )
  983. # write statistics
  984. I_iclass_add_signature(self.signatures, statistics)
  985. elif ret == 0:
  986. GMessage(
  987. parent=self,
  988. message=_("No area in category %s. Category skipped.")
  989. % stats.category,
  990. )
  991. I_iclass_free_statistics(statistics)
  992. else:
  993. GMessage(parent=self, message=_("Analysis failed."))
  994. I_iclass_free_statistics(statistics)
  995. self.UpdateChangeState(changes=False)
  996. return True
  997. def _addSuffix(self, name):
  998. suffix = _("results")
  999. return "_".join((name, suffix))
  1000. def OnSaveSigFile(self, event):
  1001. """Asks for signature file name and saves it."""
  1002. if not self.g["group"]:
  1003. GMessage(parent=self, message=_("No imagery group selected."))
  1004. return
  1005. if self.changes:
  1006. qdlg = wx.MessageDialog(
  1007. parent=self,
  1008. message=_(
  1009. "Due to recent changes in classes, "
  1010. "signatures can be outdated and should be recalculated. "
  1011. "Do you still want to continue?"
  1012. ),
  1013. caption=_("Outdated signatures"),
  1014. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
  1015. )
  1016. if qdlg.ShowModal() == wx.ID_YES:
  1017. qdlg.Destroy()
  1018. else:
  1019. qdlg.Destroy()
  1020. return
  1021. dlg = IClassSignatureFileDialog(
  1022. self, group=self.g["group"], subgroup=self.g["subgroup"], file=self.sigFile
  1023. )
  1024. if dlg.ShowModal() == wx.ID_OK:
  1025. if os.path.exists(dlg.GetFileName(fullPath=True)):
  1026. qdlg = wx.MessageDialog(
  1027. parent=self,
  1028. message=_(
  1029. "A signature file named %s already exists.\n"
  1030. "Do you want to replace it?"
  1031. )
  1032. % dlg.GetFileName(),
  1033. caption=_("File already exists"),
  1034. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
  1035. )
  1036. if qdlg.ShowModal() == wx.ID_YES:
  1037. qdlg.Destroy()
  1038. else:
  1039. qdlg.Destroy()
  1040. return
  1041. self.sigFile = dlg.GetFileName()
  1042. self.WriteSignatures(
  1043. self.signatures, self.g["group"], self.g["subgroup"], self.sigFile
  1044. )
  1045. dlg.Destroy()
  1046. def InitStatistics(self):
  1047. """Initialize variables and c structures necessary for
  1048. computing statistics.
  1049. """
  1050. self.g = {"group": None, "subgroup": None}
  1051. self.sigFile = None
  1052. self.stats_data = StatisticsData()
  1053. self.cStatisticsDict = {}
  1054. self.signatures_obj = Signature()
  1055. self.signatures = pointer(self.signatures_obj)
  1056. I_init_signatures(self.signatures, 0) # must be freed on exit
  1057. refer_obj = Ref()
  1058. self.refer = pointer(refer_obj)
  1059. I_init_group_ref(self.refer) # must be freed on exit
  1060. def WriteSignatures(self, signatures, group, subgroup, filename):
  1061. """Writes current signatures to signature file
  1062. :param signatures: signature (c structure)
  1063. :param group: imagery group
  1064. :param filename: signature file name
  1065. """
  1066. I_iclass_write_signatures(signatures, group, subgroup, filename)
  1067. def CheckInput(self, group, vector):
  1068. """Check if input is valid"""
  1069. # check if group is ok
  1070. # TODO check subgroup
  1071. if not group:
  1072. GMessage(
  1073. parent=self,
  1074. message=_("No imagery group selected. " "Operation canceled."),
  1075. )
  1076. return False
  1077. groupLayers = self.GetGroupLayers(self.g["group"], self.g["subgroup"])
  1078. nLayers = len(groupLayers)
  1079. if nLayers <= 1:
  1080. GMessage(
  1081. parent=self,
  1082. message=_(
  1083. "Group <%(group)s> does not have enough files "
  1084. "(it has %(files)d files). Operation canceled."
  1085. )
  1086. % {"group": group, "files": nLayers},
  1087. )
  1088. return False
  1089. # check if vector has any areas
  1090. if self.GetAreasCount() == 0:
  1091. GMessage(parent=self, message=_("No areas given. " "Operation canceled."))
  1092. return False
  1093. # check if vector is inside raster
  1094. regionBox = bound_box()
  1095. Vect_get_map_box(self.poMapInfo, byref(regionBox))
  1096. rasterInfo = grass.raster_info(groupLayers[0])
  1097. if (
  1098. regionBox.N > rasterInfo["north"]
  1099. or regionBox.S < rasterInfo["south"]
  1100. or regionBox.E > rasterInfo["east"]
  1101. or regionBox.W < rasterInfo["west"]
  1102. ):
  1103. GMessage(
  1104. parent=self,
  1105. message=_(
  1106. "Vector features are outside raster layers. " "Operation canceled."
  1107. ),
  1108. )
  1109. return False
  1110. return True
  1111. def GetAreasCount(self):
  1112. """Returns number of not dead areas"""
  1113. count = 0
  1114. numAreas = Vect_get_num_areas(self.poMapInfo)
  1115. for i in range(numAreas):
  1116. if Vect_area_alive(self.poMapInfo, i + 1):
  1117. count += 1
  1118. return count
  1119. def GetGroupLayers(self, group, subgroup=None):
  1120. """Get layers in subgroup (expecting same name for group and subgroup)
  1121. .. todo::
  1122. consider moving this function to core module for convenient
  1123. """
  1124. kwargs = {}
  1125. if subgroup:
  1126. kwargs["subgroup"] = subgroup
  1127. res = RunCommand("i.group", flags="g", group=group, read=True, **kwargs).strip()
  1128. if res.splitlines()[0]:
  1129. return sorted(res.splitlines())
  1130. return []
  1131. def ConvertToNull(self, name):
  1132. """Sets value which represents null values for given raster map.
  1133. :param name: raster map name
  1134. """
  1135. RunCommand("r.null", map=name, setnull=0)
  1136. def GetCurrentCategoryIdx(self):
  1137. """Returns current category number"""
  1138. return self.toolbars["iClass"].GetSelectedCategoryIdx()
  1139. def OnZoomIn(self, event):
  1140. """Enable zooming for plots"""
  1141. super(IClassMapFrame, self).OnZoomIn(event)
  1142. self.plotPanel.EnableZoom(type=1)
  1143. def OnZoomOut(self, event):
  1144. """Enable zooming for plots"""
  1145. super(IClassMapFrame, self).OnZoomOut(event)
  1146. self.plotPanel.EnableZoom(type=-1)
  1147. def OnPan(self, event):
  1148. """Enable panning for plots"""
  1149. super(IClassMapFrame, self).OnPan(event)
  1150. self.plotPanel.EnablePan()
  1151. def OnPointer(self, event):
  1152. """Set pointer mode.
  1153. .. todo::
  1154. pointers need refactoring
  1155. """
  1156. self.GetFirstWindow().SetModePointer()
  1157. self.GetSecondWindow().SetModePointer()
  1158. def GetMapManagers(self):
  1159. """Get map managers of wxIClass
  1160. :return: trainingMapManager, previewMapManager
  1161. """
  1162. return self.trainingMapManager, self.previewMapManager
  1163. class MapManager:
  1164. """Class for managing map renderer.
  1165. It is connected with iClassMapManagerToolbar.
  1166. """
  1167. def __init__(self, frame, mapWindow, Map):
  1168. """
  1169. It is expected that \a mapWindow is connected with \a Map.
  1170. :param frame: application main window
  1171. :param mapWindow: map window instance
  1172. :param map: map renderer instance
  1173. """
  1174. self.map = Map
  1175. self.frame = frame
  1176. self.mapWindow = mapWindow
  1177. self.toolbar = None
  1178. self.layerName = {}
  1179. def SetToolbar(self, toolbar):
  1180. self.toolbar = toolbar
  1181. def AddLayer(self, name, alias=None, resultsLayer=False):
  1182. """Adds layer to Map and update toolbar
  1183. :param str name: layer (raster) name
  1184. :param str resultsLayer: True if layer is temp. raster showing the results of computation
  1185. """
  1186. if resultsLayer and name in [
  1187. layer.GetName() for layer in self.map.GetListOfLayers(name=name)
  1188. ]:
  1189. self.frame.Render(self.mapWindow)
  1190. return
  1191. cmdlist = ["d.rast", "map=%s" % name]
  1192. self.map.AddLayer(
  1193. ltype="raster",
  1194. command=cmdlist,
  1195. active=True,
  1196. name=name,
  1197. hidden=False,
  1198. opacity=1.0,
  1199. render=True,
  1200. )
  1201. self.frame.Render(self.mapWindow)
  1202. if alias is not None:
  1203. self.layerName[alias] = name
  1204. name = alias
  1205. else:
  1206. self.layerName[name] = name
  1207. self.toolbar.choice.Insert(name, 0)
  1208. self.toolbar.choice.SetSelection(0)
  1209. def AddLayerRGB(self, cmd):
  1210. """Adds RGB layer and update toolbar.
  1211. :param cmd: d.rgb command as a list
  1212. """
  1213. name = []
  1214. for param in cmd:
  1215. if "=" in param:
  1216. name.append(param.split("=")[1])
  1217. name = ",".join(name)
  1218. self.map.AddLayer(
  1219. ltype="rgb",
  1220. command=cmd,
  1221. active=True,
  1222. name=name,
  1223. hidden=False,
  1224. opacity=1.0,
  1225. render=True,
  1226. )
  1227. self.frame.Render(self.mapWindow)
  1228. self.layerName[name] = name
  1229. self.toolbar.choice.Insert(name, 0)
  1230. self.toolbar.choice.SetSelection(0)
  1231. def RemoveTemporaryLayer(self, name):
  1232. """Removes temporary layer (if exists) from Map and and updates toolbar.
  1233. :param name: real name of layer
  1234. """
  1235. # check if layer is loaded
  1236. layers = self.map.GetListOfLayers(ltype="raster")
  1237. idx = None
  1238. for i, layer in enumerate(layers):
  1239. if name == layer.GetName():
  1240. idx = i
  1241. break
  1242. if idx is None:
  1243. return
  1244. # remove it from Map
  1245. self.map.RemoveLayer(name=name)
  1246. # update inner list of layers
  1247. alias = self.GetAlias(name)
  1248. if alias not in self.layerName:
  1249. return
  1250. del self.layerName[alias]
  1251. # update choice
  1252. idx = self.toolbar.choice.FindString(alias)
  1253. if idx != wx.NOT_FOUND:
  1254. self.toolbar.choice.Delete(idx)
  1255. if not self.toolbar.choice.IsEmpty():
  1256. self.toolbar.choice.SetSelection(0)
  1257. self.frame.Render(self.mapWindow)
  1258. def Render(self):
  1259. """
  1260. .. todo::
  1261. giface shoud be used instead of this method"""
  1262. self.frame.Render(self.mapWindow)
  1263. def RemoveLayer(self, name, idx):
  1264. """Removes layer from Map and update toolbar"""
  1265. name = self.layerName[name]
  1266. self.map.RemoveLayer(name=name)
  1267. del self.layerName[name]
  1268. self.toolbar.choice.Delete(idx)
  1269. if not self.toolbar.choice.IsEmpty():
  1270. self.toolbar.choice.SetSelection(0)
  1271. self.frame.Render(self.mapWindow)
  1272. def SelectLayer(self, name):
  1273. """Moves selected layer to top"""
  1274. layers = self.map.GetListOfLayers(ltype="rgb") + self.map.GetListOfLayers(
  1275. ltype="raster"
  1276. )
  1277. idx = None
  1278. for i, layer in enumerate(layers):
  1279. if self.layerName[name] == layer.GetName():
  1280. idx = i
  1281. break
  1282. if idx is not None: # should not happen
  1283. layers.append(layers.pop(idx))
  1284. choice = self.toolbar.choice
  1285. idx = choice.FindString(name)
  1286. choice.Delete(idx)
  1287. choice.Insert(name, 0)
  1288. choice.SetSelection(0)
  1289. # layers.reverse()
  1290. self.map.SetLayers(layers)
  1291. self.frame.Render(self.mapWindow)
  1292. def SetOpacity(self, name):
  1293. """Sets opacity of layers."""
  1294. name = self.layerName[name]
  1295. layers = self.map.GetListOfLayers(name=name)
  1296. if not layers:
  1297. return
  1298. # works for first layer only
  1299. oldOpacity = layers[0].GetOpacity()
  1300. dlg = SetOpacityDialog(self.frame, opacity=oldOpacity)
  1301. dlg.applyOpacity.connect(
  1302. lambda value: self._changeOpacity(layer=layers[0], opacity=value)
  1303. )
  1304. if dlg.ShowModal() == wx.ID_OK:
  1305. self._changeOpacity(layer=layers[0], opacity=dlg.GetOpacity())
  1306. dlg.Destroy()
  1307. def _changeOpacity(self, layer, opacity):
  1308. self.map.ChangeOpacity(layer=layer, opacity=opacity)
  1309. self.frame.Render(self.mapWindow)
  1310. def GetAlias(self, name):
  1311. """Returns alias for layer"""
  1312. name = [k for k, v in six.iteritems(self.layerName) if v == name]
  1313. if name:
  1314. return name[0]
  1315. return None
  1316. def SetAlias(self, original, alias):
  1317. name = self.GetAlias(original)
  1318. if name:
  1319. self.layerName[alias] = original
  1320. del self.layerName[name]
  1321. idx = self.toolbar.choice.FindString(name)
  1322. if idx != wx.NOT_FOUND:
  1323. self.toolbar.choice.SetString(idx, alias)
  1324. def test():
  1325. app = wx.App()
  1326. frame = IClassMapFrame()
  1327. frame.Show()
  1328. app.MainLoop()
  1329. if __name__ == "__main__":
  1330. test()