frame.py 51 KB

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