frame.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  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-2011 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 sys
  17. import copy
  18. import tempfile
  19. if __name__ == "__main__":
  20. sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
  21. from core import globalvar
  22. import wx
  23. from ctypes import *
  24. try:
  25. from grass.lib.imagery import *
  26. from grass.lib.vector import *
  27. haveIClass = True
  28. errMsg = ''
  29. except ImportError, e:
  30. haveIClass = False
  31. errMsg = _("Loading imagery lib failed.\n%s") % e
  32. import grass.script as grass
  33. from mapdisp import statusbar as sb
  34. from mapdisp.mapwindow import BufferedWindow
  35. from vdigit.toolbars import VDigitToolbar
  36. from gui_core.mapdisp import DoubleMapFrame
  37. from core.render import Map, MapLayer
  38. from core.gcmd import RunCommand, GMessage, GError, GWarning
  39. from gui_core.dialogs import SetOpacityDialog
  40. from dbmgr.vinfo import VectorDBInfo
  41. import grass.script as grass
  42. from iclass.digit import IClassVDigitWindow, IClassVDigit
  43. from iclass.toolbars import IClassMapToolbar, IClassMiscToolbar,\
  44. IClassToolbar, IClassMapManagerToolbar
  45. from iclass.statistics import Statistics, BandStatistics
  46. from iclass.dialogs import CategoryListCtrl, IClassCategoryManagerDialog,\
  47. IClassGroupDialog, IClassSignatureFileDialog,\
  48. IClassExportAreasDialog, IClassMapDialog
  49. from iclass.plots import PlotPanel
  50. class IClassMapFrame(DoubleMapFrame):
  51. """! wxIClass main frame
  52. It has two map windows one for digitizing training areas and one for
  53. result preview.
  54. It generates histograms, raster maps and signature files using
  55. @c I_iclass_* functions from C imagery library.
  56. It is wxGUI counterpart of old i.class module.
  57. """
  58. def __init__(self, parent = None, giface = None, title = _("GRASS GIS Supervised Classification Tool"),
  59. toolbars = ["iClassMisc", "iClassMap", "vdigit", "iClass"],
  60. size = (875, 600), name = 'IClassWindow', **kwargs):
  61. """!
  62. @param parent (no parent is expected)
  63. @param title window title
  64. @param toolbars dictionary of active toolbars (defalult value represents all toolbars)
  65. @param size default size
  66. """
  67. DoubleMapFrame.__init__(self, parent = parent, title = title,
  68. name = name,
  69. firstMap = Map(), secondMap = Map(),
  70. **kwargs)
  71. self._giface = giface
  72. self.firstMapWindow = IClassVDigitWindow(parent = self, giface = self._giface,
  73. map = self.firstMap, frame = self)
  74. self.secondMapWindow = BufferedWindow(parent = self, giface = self._giface,
  75. Map = self.secondMap, frame = self)
  76. self.MapWindow = self.firstMapWindow # current by default
  77. self._bindWindowsActivation()
  78. self.SetSize(size)
  79. #
  80. # Add toolbars
  81. #
  82. toolbarsCopy = toolbars[:]
  83. if sys.platform == 'win32':
  84. self.AddToolbar(toolbarsCopy.pop(1))
  85. toolbarsCopy.reverse()
  86. else:
  87. self.AddToolbar(toolbarsCopy.pop(0))
  88. for toolb in toolbarsCopy:
  89. self.AddToolbar(toolb)
  90. self.firstMapWindow.SetToolbar(self.toolbars['vdigit'])
  91. self.GetMapToolbar().GetActiveMapTool().Bind(wx.EVT_CHOICE, self.OnUpdateActive)
  92. #
  93. # Add statusbar
  94. #
  95. # items for choice
  96. self.statusbarItems = [sb.SbCoordinates,
  97. sb.SbRegionExtent,
  98. sb.SbCompRegionExtent,
  99. sb.SbShowRegion,
  100. sb.SbAlignExtent,
  101. sb.SbResolution,
  102. sb.SbDisplayGeometry,
  103. sb.SbMapScale,
  104. sb.SbGoTo,
  105. sb.SbProjection]
  106. # create statusbar and its manager
  107. statusbar = self.CreateStatusBar(number = 4, style = 0)
  108. statusbar.SetStatusWidths([-5, -2, -1, -1])
  109. self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar)
  110. # fill statusbar manager
  111. self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar)
  112. self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2))
  113. self.statusbarManager.AddStatusbarItem(sb.SbRender(self, statusbar = statusbar, position = 3))
  114. self.statusbarManager.Update()
  115. self.trainingMapManager = MapManager(self, mapWindow = self.GetFirstWindow(),
  116. Map = self.GetFirstMap())
  117. self.previewMapManager = MapManager(self, mapWindow = self.GetSecondWindow(),
  118. Map = self.GetSecondMap())
  119. self.InitStatistics()
  120. self.changes = False
  121. self.exportVector = None
  122. # dialogs
  123. self.dialogs = dict()
  124. self.dialogs['classManager'] = None
  125. # just to make digitizer happy
  126. self.dialogs['attributes'] = None
  127. self.dialogs['category'] = None
  128. # PyPlot init
  129. self.plotPanel = PlotPanel(self, statDict = self.statisticsDict,
  130. statList = self.statisticsList)
  131. self._addPanes()
  132. self._mgr.Update()
  133. self.trainingMapManager.SetToolbar(self.toolbars['iClassTrainingMapManager'])
  134. self.previewMapManager.SetToolbar(self.toolbars['iClassPreviewMapManager'])
  135. # default action
  136. self.OnPan(event = None)
  137. wx.CallAfter(self.AddTrainingAreaMap)
  138. #self.dialogs['category'] = None
  139. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  140. self.SendSizeEvent()
  141. def OnCloseWindow(self, event):
  142. verbosity = os.getenv('GRASS_VERBOSE', '2')
  143. os.environ['GRASS_VERBOSE'] = '0' # be silent
  144. self.GetFirstWindow().digit.GetDisplay().CloseMap()
  145. os.environ['GRASS_VERBOSE'] = verbosity
  146. self.Destroy()
  147. def __del__(self):
  148. """! Frees C structs and removes vector map and all raster maps."""
  149. I_free_signatures(self.signatures)
  150. I_free_group_ref(self.refer)
  151. for st in self.cStatisticsDict.values():
  152. I_iclass_free_statistics(st)
  153. self.RemoveTempVector()
  154. for i in self.statisticsList:
  155. self.RemoveTempRaster(self.statisticsDict[i].rasterName)
  156. def OnHelp(self, event):
  157. """!Show help page"""
  158. self._giface.Help(entry = 'wxGUI.iclass')
  159. def CreateTempVector(self):
  160. """!Create temporary vector map for training areas"""
  161. vectorPath = grass.tempfile(create = False)
  162. vectorName = 'trAreas' + os.path.basename(vectorPath).replace('.','')
  163. cmd = ('v.edit', {'tool': 'create',
  164. 'map': vectorName})
  165. ret = RunCommand(prog = cmd[0],
  166. parent = self,
  167. overwrite = True,
  168. **cmd[1])
  169. if ret != 0:
  170. return False
  171. return vectorName
  172. def RemoveTempVector(self):
  173. """!Removes temporary vector map with training areas"""
  174. ret = RunCommand(prog = 'g.remove',
  175. parent = self,
  176. vect = self.trainingAreaVector)
  177. if ret != 0:
  178. return False
  179. return True
  180. def RemoveTempRaster(self, raster):
  181. """!Removes temporary raster maps"""
  182. self.GetFirstMap().Clean()
  183. self.GetSecondMap().Clean()
  184. ret = RunCommand(prog = 'g.remove',
  185. parent = self,
  186. rast = raster)
  187. if ret != 0:
  188. return False
  189. return True
  190. def AddToolbar(self, name):
  191. """!Add defined toolbar to the window
  192. Currently known toolbars are:
  193. - 'iClassMap' - basic map toolbar
  194. - 'iClass' - iclass tools
  195. - 'iClassMisc' - miscellaneous (help)
  196. - 'vdigit' - digitizer toolbar (areas)
  197. Toolbars 'iClassPreviewMapManager' are added in _addPanes().
  198. """
  199. if name == "iClassMap":
  200. self.toolbars[name] = IClassMapToolbar(self)
  201. self._mgr.AddPane(self.toolbars[name],
  202. wx.aui.AuiPaneInfo().
  203. Name(name).Caption(_("Map Toolbar")).
  204. ToolbarPane().Top().
  205. LeftDockable(False).RightDockable(False).
  206. BottomDockable(False).TopDockable(True).
  207. CloseButton(False).Layer(2).Row(1).
  208. BestSize((self.toolbars[name].GetBestSize())))
  209. if name == "iClass":
  210. self.toolbars[name] = IClassToolbar(self)
  211. self._mgr.AddPane(self.toolbars[name],
  212. wx.aui.AuiPaneInfo().
  213. Name(name).Caption(_("IClass Toolbar")).
  214. ToolbarPane().Top().
  215. LeftDockable(False).RightDockable(False).
  216. BottomDockable(False).TopDockable(True).
  217. CloseButton(False).Layer(2).Row(2).
  218. BestSize((self.toolbars[name].GetBestSize())))
  219. if name == "iClassMisc":
  220. self.toolbars[name] = IClassMiscToolbar(self)
  221. self._mgr.AddPane(self.toolbars[name],
  222. wx.aui.AuiPaneInfo().
  223. Name(name).Caption(_("IClass Misc Toolbar")).
  224. ToolbarPane().Top().
  225. LeftDockable(False).RightDockable(False).
  226. BottomDockable(False).TopDockable(True).
  227. CloseButton(False).Layer(2).Row(1).
  228. BestSize((self.toolbars[name].GetBestSize())))
  229. if name == "vdigit":
  230. self.toolbars[name] = VDigitToolbar(parent = self, MapWindow = self.GetFirstWindow(),
  231. digitClass = IClassVDigit, giface = self._giface,
  232. tools = ['addArea', 'moveVertex', 'addVertex',
  233. 'removeVertex', 'editLine', 'moveLine',
  234. 'deleteLine', 'deleteArea',
  235. 'undo', 'redo'])
  236. self._mgr.AddPane(self.toolbars[name],
  237. wx.aui.AuiPaneInfo().
  238. Name(name).Caption(_("Digitization Toolbar")).
  239. ToolbarPane().Top().
  240. LeftDockable(False).RightDockable(False).
  241. BottomDockable(False).TopDockable(True).
  242. CloseButton(False).Layer(2).Row(2).
  243. BestSize((self.toolbars[name].GetBestSize())))
  244. def _addPanes(self):
  245. """!Add mapwindows and toolbars to aui manager"""
  246. if sys.platform == 'win32':
  247. self._addPaneMapWindow(name = 'training')
  248. self._addPaneToolbar(name = 'iClassTrainingMapManager')
  249. self._addPaneMapWindow(name = 'preview')
  250. self._addPaneToolbar(name = 'iClassPreviewMapManager')
  251. else:
  252. self._addPaneToolbar(name = 'iClassPreviewMapManager')
  253. self._addPaneMapWindow(name = 'preview')
  254. self._addPaneToolbar(name = 'iClassTrainingMapManager')
  255. self._addPaneMapWindow(name = 'training')
  256. self._mgr.AddPane(self.plotPanel, wx.aui.AuiPaneInfo().
  257. Name("plots").Caption(_("Plots")).
  258. Dockable(False).Floatable(False).CloseButton(False).
  259. Left().Layer(1).BestSize((400, -1)))
  260. def _addPaneToolbar(self, name):
  261. if name == 'iClassPreviewMapManager':
  262. parent = self.previewMapManager
  263. else:
  264. parent = self.trainingMapManager
  265. self.toolbars[name] = IClassMapManagerToolbar(self, parent)
  266. self._mgr.AddPane(self.toolbars[name],
  267. wx.aui.AuiPaneInfo().ToolbarPane().Movable().
  268. Name(name).
  269. CloseButton(False).Center().Layer(0).
  270. BestSize((self.toolbars[name].GetBestSize())))
  271. def _addPaneMapWindow(self, name):
  272. if name == 'preview':
  273. window = self.GetSecondWindow()
  274. caption = _("Preview Display")
  275. else:
  276. window = self.GetFirstWindow()
  277. caption = _("Training Areas Display")
  278. self._mgr.AddPane(window, wx.aui.AuiPaneInfo().
  279. Name(name).Caption(caption).
  280. Dockable(False).Floatable(False).CloseButton(False).
  281. Center().Layer(0))
  282. def IsStandalone(self):
  283. """!Check if Map display is standalone"""
  284. return True
  285. def OnUpdateActive(self, event):
  286. """!
  287. @todo move to DoubleMapFrame?
  288. """
  289. if self.GetMapToolbar().GetActiveMap() == 0:
  290. self.MapWindow = self.firstMapWindow
  291. self.Map = self.firstMap
  292. else:
  293. self.MapWindow = self.secondMapWindow
  294. self.Map = self.secondMap
  295. self.UpdateActive(self.MapWindow)
  296. # for wingrass
  297. if os.name == 'nt':
  298. self.MapWindow.SetFocus()
  299. def UpdateActive(self, win):
  300. """!
  301. @todo move to DoubleMapFrame?
  302. """
  303. mapTb = self.GetMapToolbar()
  304. # optionally disable tool zoomback tool
  305. mapTb.Enable('zoomBack', enable = (len(self.MapWindow.zoomhistory) > 1))
  306. if mapTb.GetActiveMap() != (win == self.secondMapWindow):
  307. mapTb.SetActiveMap((win == self.secondMapWindow))
  308. self.StatusbarUpdate()
  309. def GetMapToolbar(self):
  310. """!Returns toolbar with zooming tools"""
  311. return self.toolbars['iClassMap']
  312. def GetClassColor(self, cat):
  313. """!Get class color as string
  314. @param cat class category
  315. @return 'R:G:B'
  316. """
  317. if cat in self.statisticsDict:
  318. return self.statisticsDict[cat].color
  319. return '0:0:0'
  320. def OnZoomMenu(self, event):
  321. """!Popup Zoom menu """
  322. zoommenu = wx.Menu()
  323. # Add items to the menu
  324. i = 0
  325. for label, handler in ((_('Zoom to computational region'), self.OnZoomToWind),
  326. (None, None),
  327. (_('Adjust Training Area Display to Preview Display'), self.OnZoomToPreview),
  328. (_('Adjust Preview display to Training Area Display'), self.OnZoomToTraining),
  329. (_("Display synchronization ON"), lambda event: self.SetBindRegions(True)),
  330. (_("Display synchronization OFF"), lambda event: self.SetBindRegions(False))):
  331. if label is None:
  332. zoommenu.AppendSeparator()
  333. continue
  334. item = wx.MenuItem(zoommenu, wx.ID_ANY, label)
  335. zoommenu.AppendItem(item)
  336. self.Bind(wx.EVT_MENU, handler, item)
  337. if i == 3:
  338. item.Enable(not self._bindRegions)
  339. elif i == 4:
  340. item.Enable(self._bindRegions)
  341. i += 1
  342. # Popup the menu. If an item is selected then its handler
  343. # will be called before PopupMenu returns.
  344. self.PopupMenu(zoommenu)
  345. zoommenu.Destroy()
  346. def OnZoomToTraining(self, event):
  347. """!Set preview display to match extents of training display """
  348. if not self.MapWindow == self.GetSecondWindow():
  349. self.MapWindow = self.GetSecondWindow()
  350. self.Map = self.GetSecondMap()
  351. self.UpdateActive(self.GetSecondWindow())
  352. newreg = self.firstMap.GetCurrentRegion()
  353. self.GetSecondMap().region = copy.copy(newreg)
  354. self.Render(self.GetSecondWindow())
  355. def OnZoomToPreview(self, event):
  356. """!Set preview display to match extents of training display """
  357. if not self.MapWindow == self.GetFirstWindow():
  358. self.MapWindow = self.GetFirstWindow()
  359. self.Map = self.GetFirstMap()
  360. self.UpdateActive(self.GetFirstWindow())
  361. newreg = self.GetSecondMap().GetCurrentRegion()
  362. self.GetFirstMap().region = copy.copy(newreg)
  363. self.Render(self.GetFirstWindow())
  364. def OnAddBands(self, event):
  365. """!Add imagery group"""
  366. dlg = IClassGroupDialog(self, group = self.group)
  367. if dlg.ShowModal() == wx.ID_OK:
  368. self.SetGroup(dlg.GetGroup())
  369. dlg.Destroy()
  370. def SetGroup(self, name):
  371. """!Set imagery group"""
  372. group = grass.find_file(name = name, element = 'group')
  373. if group['name']:
  374. self.group = group['name']
  375. else:
  376. GError(_("Group <%s> not found") % name, parent = self)
  377. def OnImportAreas(self, event):
  378. """!Import training areas"""
  379. # check if we have any changes
  380. if self.GetAreasCount() or self.statisticsList:
  381. qdlg = wx.MessageDialog(parent = self,
  382. message = _("All changes will be lost. "
  383. "Do you want to continue?") ,
  384. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  385. if qdlg.ShowModal() == wx.ID_NO:
  386. qdlg.Destroy()
  387. return
  388. qdlg.Destroy()
  389. dlg = IClassMapDialog(self, title = _("Import vector map"), element = 'vector')
  390. if dlg.ShowModal() == wx.ID_OK:
  391. vName = dlg.GetMap()
  392. warning = self._checkImportedTopo(vName)
  393. if warning:
  394. GMessage(parent = self, message = warning)
  395. self.ImportAreas(vName)
  396. dlg.Destroy()
  397. def _checkImportedTopo(self, vector):
  398. """!Check if imported vector map has areas
  399. @param vector vector map name
  400. @return warning message (empty if topology is ok)
  401. """
  402. topo = grass.vector_info_topo(map = vector)
  403. warning = ''
  404. if topo['areas'] == 0:
  405. warning = _("No areas in vector map <%s>.\n" % vector)
  406. if topo['points'] or topo['lines']:
  407. warning +=_("Vector map <%s> contains points or lines, "
  408. "these features are ignored." % vector)
  409. return warning
  410. def ImportAreas(self, vector):
  411. """!Import training areas.
  412. If table connected, try load certain columns to class manager
  413. @param vector vector map name
  414. """
  415. wx.BeginBusyCursor()
  416. wx.Yield()
  417. mapLayer = self.toolbars['vdigit'].mapLayer
  418. # set mapLayer temporarily to None
  419. # to avoid 'save changes' code in vdigit.toolbars
  420. self.toolbars['vdigit'].mapLayer = None
  421. ret = self.toolbars['vdigit'].StopEditing()
  422. if not ret:
  423. wx.EndBusyCursor()
  424. return False
  425. # check if attribute table really exists (fix broken connections)
  426. for layer in grass.vector_db(vector):
  427. if 0 != RunCommand('v.db.select', map = vector, where = "0 = 1"):
  428. GWarning(_("Vector map <%s>: no attribute table found") % vector,
  429. parent = self)
  430. RunCommand('v.db.connect', flags = 'd', map = vector, layer = layer,
  431. quiet = True)
  432. if 0 != RunCommand('g.copy',
  433. vect = [vector, self.trainingAreaVector],
  434. overwrite = True, quiet = True,
  435. parent = self):
  436. wx.EndBusyCursor()
  437. return False
  438. ret = self.toolbars['vdigit'].StartEditing(mapLayer)
  439. if not ret:
  440. wx.EndBusyCursor()
  441. return False
  442. self.poMapInfo = self.GetFirstWindow().digit.GetDisplay().poMapInfo
  443. # remove temporary rasters
  444. for i in self.statisticsList:
  445. self.RemoveTempRaster(self.statisticsDict[i].rasterName)
  446. # clear current statistics
  447. self.statisticsDict.clear()
  448. del self.statisticsList[:] # not ...=[] !
  449. # reset plots
  450. self.plotPanel.Reset()
  451. self.GetFirstWindow().UpdateMap(render = False, renderVector = True)
  452. self.ImportClasses(vector)
  453. # should be saved in attribute table?
  454. self.toolbars['iClass'].UpdateStddev(1.5)
  455. wx.EndBusyCursor()
  456. return True
  457. def ImportClasses(self, vector):
  458. """!If imported map has table, try to import certain columns to class manager"""
  459. # check connection
  460. dbInfo = VectorDBInfo(vector)
  461. connected = (len(dbInfo.layers.keys()) > 0)
  462. # remove attribute table of temporary vector, we don't need it
  463. if connected:
  464. RunCommand('v.db.droptable',
  465. flags = 'f',
  466. map = self.trainingAreaVector)
  467. # we use first layer with table, TODO: user should choose
  468. layer = None
  469. for key in dbInfo.layers.keys():
  470. if dbInfo.GetTable(key):
  471. layer = key
  472. # get columns to check if we can use them
  473. # TODO: let user choose which columns mean what
  474. if layer is not None:
  475. columns = dbInfo.GetColumns(table = dbInfo.GetTable(layer))
  476. else:
  477. columns = []
  478. # get class manager
  479. if self.dialogs['classManager'] is None:
  480. self.dialogs['classManager'] = IClassCategoryManagerDialog(self)
  481. listCtrl = self.dialogs['classManager'].GetListCtrl()
  482. # unable to load data (no connection, table, right columns)
  483. if not connected or layer is None or \
  484. 'class' not in columns or \
  485. 'color' not in columns:
  486. # no table connected
  487. cats = RunCommand('v.category',
  488. input = vector,
  489. layer = 1, # set layer?
  490. # type = ['centroid', 'area'] ?
  491. option = "print",
  492. read = True)
  493. cats = map(int, cats.strip().split())
  494. cats = sorted(list(set(cats)))
  495. for cat in cats:
  496. listCtrl.AddCategory(cat = cat, name = 'class_%d' % cat, color = "0:0:0")
  497. # connection, table and columns exists
  498. else:
  499. columns = ['cat', 'class', 'color']
  500. ret = RunCommand('v.db.select',
  501. quiet = True,
  502. parent = self,
  503. flags = 'c',
  504. map = vector,
  505. layer = 1,
  506. columns = ','.join(columns),
  507. read = True)
  508. records = ret.strip().split('\n')
  509. for record in records:
  510. record = record.split('|')
  511. listCtrl.AddCategory(cat = int(record[0]), name = record[1], color = record[2])
  512. def OnExportAreas(self, event):
  513. """!Export training areas"""
  514. if self.GetAreasCount() == 0:
  515. GMessage(parent = self, message = _("No training areas to export."))
  516. return
  517. dlg = IClassExportAreasDialog(self, vectorName = self.exportVector)
  518. if dlg.ShowModal() == wx.ID_OK:
  519. vName = dlg.GetVectorName()
  520. self.exportVector = vName
  521. withTable = dlg.WithTable()
  522. dlg.Destroy()
  523. if self.ExportAreas(vectorName = vName, withTable = withTable):
  524. GMessage(_("%d training areas (%d classes) exported to vector map <%s>.") % \
  525. (self.GetAreasCount(), len(self.statisticsList),
  526. self.exportVector), parent = self)
  527. def ExportAreas(self, vectorName, withTable):
  528. """!Export training areas to new vector map (with attribute table).
  529. @param vectorName name of exported vector map
  530. @param withTable true if attribute table is required
  531. """
  532. wx.BeginBusyCursor()
  533. wx.Yield()
  534. # close, build, copy and open again the temporary vector
  535. displayDriver = self.GetFirstWindow().digit.GetDisplay()
  536. displayDriver.CloseMap()
  537. if '@' in vectorName:
  538. vectorName = vectorName.split('@')[0]
  539. if 0 != RunCommand('g.copy',
  540. vect = [self.trainingAreaVector, vectorName],
  541. overwrite = True, quiet = True, parent = self):
  542. wx.EndBusyCursor()
  543. return False
  544. # remove connection if exists
  545. dbinfo = grass.vector_db(vectorName)
  546. if dbinfo:
  547. for layer in dbinfo.keys():
  548. RunCommand('v.db.connect', flags = 'd', map = vectorName, layer = layer)
  549. mapset = grass.gisenv()['MAPSET']
  550. self.poMapInfo = displayDriver.OpenMap(name = self.trainingAreaVector, mapset = mapset)
  551. if not withTable:
  552. wx.EndBusyCursor()
  553. return False
  554. # add new table
  555. columns = ["class varchar(30)",
  556. "color varchar(11)",
  557. "n_cells integer",]
  558. nbands = len(self.GetGroupLayers(self.group))
  559. for statistic, format in (("min", "integer"), ("mean", "double precision"), ("max", "integer")):
  560. for i in range(nbands):
  561. # 10 characters limit?
  562. columns.append("band%(band)d_%(stat)s %(format)s" % {'band' : i + 1,
  563. 'stat' : statistic,
  564. 'format' : format})
  565. if 0 != RunCommand('v.db.addtable',
  566. map = vectorName,
  567. columns = columns,
  568. parent = self):
  569. wx.EndBusyCursor()
  570. return False
  571. # populate table
  572. for cat in self.statisticsList:
  573. stat = self.statisticsDict[cat]
  574. self._runDBUpdate(map = vectorName, column = "class", value = stat.name, cat = cat)
  575. self._runDBUpdate(map = vectorName, column = "color", value = stat.color, cat = cat)
  576. if not stat.IsReady():
  577. continue
  578. self._runDBUpdate(map = vectorName, column = "n_cells",value = stat.ncells, cat = cat)
  579. for i in range(nbands):
  580. self._runDBUpdate(map = vectorName, column = "band%d_min" % (i + 1), value = stat.bands[i].min, cat = cat)
  581. self._runDBUpdate(map = vectorName, column = "band%d_mean" % (i + 1), value = stat.bands[i].mean, cat = cat)
  582. self._runDBUpdate(map = vectorName, column = "band%d_max" % (i + 1), value = stat.bands[i].max, cat = cat)
  583. wx.EndBusyCursor()
  584. return True
  585. def _runDBUpdate(self, map, column, value, cat):
  586. """!Helper function for calling v.db.update.
  587. @param map vector map name
  588. @param column name of updated column
  589. @param value new value
  590. @param cat which category to update
  591. @return returncode (0 is OK)
  592. """
  593. ret = RunCommand('v.db.update',
  594. map = map,
  595. layer = 1,
  596. column = column,
  597. value = value,
  598. where = "cat = %d" % cat)
  599. return ret
  600. def OnCategoryManager(self, event):
  601. """!Show category management dialog"""
  602. if self.dialogs['classManager'] is None:
  603. dlg = IClassCategoryManagerDialog(self)
  604. dlg.Show()
  605. self.dialogs['classManager'] = dlg
  606. else:
  607. if not self.dialogs['classManager'].IsShown():
  608. self.dialogs['classManager'].Show()
  609. def CategoryChanged(self, currentCat):
  610. """!Updates everything which depends on current category.
  611. Updates number of stddev, histograms, layer in preview display.
  612. """
  613. nstd = self.statisticsDict[currentCat].nstd
  614. self.toolbars['iClass'].UpdateStddev(nstd)
  615. self.plotPanel.UpdateCategory(currentCat)
  616. self.plotPanel.OnPlotTypeSelected(None)
  617. name = self.statisticsDict[currentCat].rasterName
  618. name = self.previewMapManager.GetAlias(name)
  619. if name:
  620. self.previewMapManager.SelectLayer(name)
  621. def DeleteAreas(self, cats):
  622. """!Removes all training areas of given categories
  623. @param cats list of categories to be deleted
  624. """
  625. self.firstMapWindow.digit.DeleteAreasByCat(cats)
  626. self.firstMapWindow.UpdateMap(render=False, renderVector=True)
  627. def HighlightCategory(self, cats):
  628. """!Highlight araes given by category"""
  629. self.firstMapWindow.digit.GetDisplay().SetSelected(cats, layer = 1)
  630. self.firstMapWindow.UpdateMap(render=False, renderVector=True)
  631. def ZoomToAreasByCat(self, cat):
  632. """!Zoom to areas given by category"""
  633. n, s, w, e = self.GetFirstWindow().digit.GetDisplay().GetRegionSelected()
  634. self.GetFirstMap().GetRegion(n = n, s = s, w = w, e = e, update = True)
  635. self.GetFirstMap().AdjustRegion()
  636. self.GetFirstMap().AlignExtentFromDisplay()
  637. self.GetFirstWindow().UpdateMap(render = True, renderVector = True)
  638. def UpdateRasterName(self, newName, cat):
  639. """!Update alias of raster map when category name is changed"""
  640. origName = self.statisticsDict[cat].rasterName
  641. self.previewMapManager.SetAlias(origName, newName)
  642. def StddevChanged(self, cat, nstd):
  643. """!Standard deviation multiplier changed, rerender map, histograms"""
  644. stat = self.statisticsDict[cat]
  645. stat.nstd = nstd
  646. if not stat.IsReady():
  647. return
  648. raster = stat.rasterName
  649. cstat = self.cStatisticsDict[cat]
  650. I_iclass_statistics_set_nstd(cstat, nstd)
  651. I_iclass_create_raster(cstat, self.refer, raster)
  652. self.Render(self.GetSecondWindow())
  653. stat.SetBandStatistics(cstat)
  654. self.plotPanel.StddevChanged()
  655. def UpdateChangeState(self, changes):
  656. """!Informs if any important changes happened
  657. since last analysis computation.
  658. """
  659. self.changes = changes
  660. def AddRasterMap(self, name, firstMap = True, secondMap = True):
  661. """!Add raster map to Map"""
  662. cmdlist = ['d.rast', 'map=%s' % name]
  663. if firstMap:
  664. self.GetFirstMap().AddLayer(ltype='raster', command=cmdlist, active=True,
  665. name=name, hidden=False, opacity=1.0, render=False)
  666. self.Render(self.GetFirstWindow())
  667. if secondMap:
  668. self.GetSecondMap().AddLayer(ltype='raster', command=cmdlist, active=True,
  669. name=name, hidden=False, opacity=1.0, render=False)
  670. self.Render(self.GetSecondWindow())
  671. def AddTrainingAreaMap(self):
  672. """!Add vector map with training areas to Map (training
  673. sub-display)"""
  674. vname = self.CreateTempVector()
  675. if vname:
  676. self.trainingAreaVector = vname
  677. else:
  678. GMessage(parent = self, message = _("Failed to create temporary vector map."))
  679. return
  680. mapLayer = self.GetFirstMap().AddLayer(ltype = 'vector',
  681. command = ['d.vect', 'map=%s' % vname],
  682. name = vname, active = False)
  683. self.toolbars['vdigit'].StartEditing(mapLayer)
  684. self.poMapInfo = self.GetFirstWindow().digit.GetDisplay().poMapInfo
  685. self.Render(self.GetFirstWindow())
  686. def OnRunAnalysis(self, event):
  687. """!Run analysis and update plots"""
  688. if self.RunAnalysis():
  689. currentCat = self.GetCurrentCategoryIdx()
  690. self.plotPanel.UpdatePlots(group = self.group, currentCat = currentCat,
  691. statDict = self.statisticsDict,
  692. statList = self.statisticsList)
  693. def RunAnalysis(self):
  694. """!Run analysis
  695. Calls C functions to compute all statistics and creates raster maps.
  696. Signatures are created but signature file is not.
  697. """
  698. if not self.CheckInput(group = self.group, vector = self.trainingAreaVector):
  699. return
  700. for statistic in self.cStatisticsDict.values():
  701. I_iclass_free_statistics(statistic)
  702. self.cStatisticsDict = {}
  703. # init Ref struct with the files in group */
  704. I_free_group_ref(self.refer)
  705. if (not I_iclass_init_group(self.group, self.refer)):
  706. return False
  707. I_free_signatures(self.signatures)
  708. I_iclass_init_signatures(self.signatures, self.refer)
  709. cats = self.statisticsList[:]
  710. for i in cats:
  711. stats = self.statisticsDict[i]
  712. statistics_obj = IClass_statistics()
  713. statistics = pointer(statistics_obj)
  714. I_iclass_init_statistics(statistics,
  715. stats.category,
  716. stats.name,
  717. stats.color,
  718. stats.nstd)
  719. ret = I_iclass_analysis(statistics, self.refer, self.poMapInfo, "1",
  720. self.group, stats.rasterName)
  721. if ret > 0:
  722. # tests
  723. self.cStatisticsDict[i] = statistics
  724. stats.SetStatistics(statistics)
  725. stats.SetReady()
  726. self.statisticsDict[stats.category] = stats
  727. self.ConvertToNull(name = stats.rasterName)
  728. self.previewMapManager.AddLayer(name = stats.rasterName,
  729. alias = stats.name, resultsLayer = True)
  730. # write statistics
  731. I_iclass_add_signature(self.signatures, statistics)
  732. elif ret == 0:
  733. GMessage(parent = self, message = _("No area in category %s. Category skipped.") % stats.category)
  734. I_iclass_free_statistics(statistics)
  735. else:
  736. GMessage(parent = self, message = _("Analysis failed."))
  737. I_iclass_free_statistics(statistics)
  738. self.UpdateChangeState(changes = False)
  739. return True
  740. def OnSaveSigFile(self, event):
  741. """!Asks for signature file name and saves it."""
  742. if not self.group:
  743. GMessage(parent = self, message = _("No imagery group selected."))
  744. return
  745. if self.changes:
  746. qdlg = wx.MessageDialog(parent = self,
  747. message = _("Due to recent changes in classes, "
  748. "signatures can be outdated and should be recalculated. "
  749. "Do you still want to continue?") ,
  750. caption = _("Outdated signatures"),
  751. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  752. if qdlg.ShowModal() == wx.ID_YES:
  753. qdlg.Destroy()
  754. else:
  755. qdlg.Destroy()
  756. return
  757. dlg = IClassSignatureFileDialog(self, group = self.group, file = self.sigFile)
  758. if dlg.ShowModal() == wx.ID_OK:
  759. if os.path.exists(dlg.GetFileName(fullPath = True)):
  760. qdlg = wx.MessageDialog(parent = self,
  761. message = _("A signature file named %s already exists.\n"
  762. "Do you want to replace it?") % dlg.GetFileName(),
  763. caption = _("File already exists"),
  764. style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  765. if qdlg.ShowModal() == wx.ID_YES:
  766. qdlg.Destroy()
  767. else:
  768. qdlg.Destroy()
  769. return
  770. self.sigFile = dlg.GetFileName()
  771. self.WriteSignatures(self.signatures, self.group, self.sigFile)
  772. dlg.Destroy()
  773. def InitStatistics(self):
  774. """!Initialize variables and c structures neccessary for
  775. computing statistics.
  776. """
  777. self.group = None
  778. self.sigFile = None
  779. self.statisticsDict = {}
  780. self.statisticsList = []
  781. self.cStatisticsDict = {}
  782. self.signatures_obj = Signature()
  783. self.signatures = pointer(self.signatures_obj)
  784. I_init_signatures(self.signatures, 0) # must be freed on exit
  785. refer_obj = Ref()
  786. self.refer = pointer(refer_obj)
  787. I_init_group_ref(self.refer) # must be freed on exit
  788. def WriteSignatures(self, signatures, group, filename):
  789. """!Writes current signatures to signature file
  790. @param signatures signature (c structure)
  791. @param group imagery group
  792. @param filename signature file name
  793. """
  794. I_iclass_write_signatures(signatures, group, group, filename)
  795. def CheckInput(self, group, vector):
  796. """!Check if input is valid"""
  797. # check if group is ok
  798. if not group:
  799. GMessage(parent = self,
  800. message = _("No imagery group selected. "
  801. "Operation canceled."))
  802. return False
  803. groupLayers = self.GetGroupLayers(group)
  804. nLayers = len(groupLayers)
  805. if nLayers <= 1:
  806. GMessage(parent = self,
  807. message = _("Group <%(group)s> does not have enough files "
  808. "(it has %(files)d files). Operation canceled.") % \
  809. { 'group' : group,
  810. 'files' : nLayers })
  811. return False
  812. #check if vector has any areas
  813. if self.GetAreasCount() == 0:
  814. GMessage(parent = self,
  815. message = _("No areas given. "
  816. "Operation canceled."))
  817. return False
  818. # check if vector is inside raster
  819. regionBox = bound_box()
  820. Vect_get_map_box(self.poMapInfo, byref(regionBox))
  821. rasterInfo = grass.raster_info(groupLayers[0])
  822. if regionBox.N > rasterInfo['north'] or \
  823. regionBox.S < rasterInfo['south'] or \
  824. regionBox.E > rasterInfo['east'] or \
  825. regionBox.W < rasterInfo['west']:
  826. GMessage(parent = self,
  827. message = _("Vector features are outside raster layers. "
  828. "Operation canceled."))
  829. return False
  830. return True
  831. def GetAreasCount(self):
  832. """!Returns number of not dead areas"""
  833. count = 0
  834. numAreas = Vect_get_num_areas(self.poMapInfo)
  835. for i in range(numAreas):
  836. if Vect_area_alive(self.poMapInfo, i + 1):
  837. count += 1
  838. return count
  839. def GetGroupLayers(self, group):
  840. """! Get layers in group
  841. @todo consider moving this function to core module for convenient
  842. """
  843. res = RunCommand('i.group',
  844. flags = 'g',
  845. group = group,
  846. read = True).strip()
  847. if res.split('\n')[0]:
  848. return res.split('\n')
  849. return []
  850. def ConvertToNull(self, name):
  851. """! Sets value which represents null values for given raster map.
  852. @param name raster map name
  853. """
  854. RunCommand('r.null',
  855. map = name,
  856. setnull = 0)
  857. def GetCurrentCategoryIdx(self):
  858. """!Returns current category number"""
  859. return self.toolbars['iClass'].GetSelectedCategoryIdx()
  860. def OnZoomIn(self, event):
  861. """!Enable zooming for plots"""
  862. super(IClassMapFrame, self).OnZoomIn(event)
  863. self.plotPanel.EnableZoom(type = 1)
  864. def OnZoomOut(self, event):
  865. """!Enable zooming for plots"""
  866. super(IClassMapFrame, self).OnZoomOut(event)
  867. self.plotPanel.EnableZoom(type = -1)
  868. def OnPan(self, event):
  869. """!Enable panning for plots"""
  870. super(IClassMapFrame, self).OnPan(event)
  871. self.plotPanel.EnablePan()
  872. def OnPointer(self, event):
  873. """!Set pointer mode.
  874. @fixme: needs refactoring
  875. """
  876. toolbar = self.GetMapToolbar()
  877. self.SwitchTool(toolbar, event)
  878. self.GetFirstWindow().mouse['use'] = 'pointer'
  879. class MapManager:
  880. """! Class for managing map renderer.
  881. It is connected with iClassMapManagerToolbar.
  882. """
  883. def __init__(self, frame, mapWindow, Map):
  884. """!
  885. It is expected that \a mapWindow is conected with \a Map.
  886. @param frame application main window
  887. @param mapWindow map window instance
  888. @param Map map renderer instance
  889. """
  890. self.map = Map
  891. self.frame = frame
  892. self.mapWindow = mapWindow
  893. self.toolbar = None
  894. self.layerName = {}
  895. def SetToolbar(self, toolbar):
  896. self.toolbar = toolbar
  897. def AddLayer(self, name, alias = None, resultsLayer = False):
  898. """!Adds layer to Map and update toolbar
  899. @param name layer (raster) name
  900. @param resultsLayer True if layer is temp. raster showing the results of computation
  901. """
  902. if (resultsLayer and
  903. name in [l.GetName() for l in self.map.GetListOfLayers(name = name)]):
  904. self.frame.Render(self.mapWindow)
  905. return
  906. cmdlist = ['d.rast', 'map=%s' % name]
  907. self.map.AddLayer(ltype = 'raster', command = cmdlist, active = True,
  908. name = name, hidden = False, opacity = 1.0, render = True)
  909. self.frame.Render(self.mapWindow)
  910. if alias is not None:
  911. alias = self._addSuffix(alias)
  912. self.layerName[alias] = name
  913. name = alias
  914. else:
  915. self.layerName[name] = name
  916. self.toolbar.choice.Insert(name, 0)
  917. self.toolbar.choice.SetSelection(0)
  918. def AddLayerRGB(self, cmd):
  919. """!Adds RGB layer and update toolbar.
  920. @param cmd d.rgb command as a list
  921. """
  922. name = []
  923. for param in cmd:
  924. if '=' in param:
  925. name.append(param.split('=')[1])
  926. name = ','.join(name)
  927. self.map.AddLayer(ltype = 'rgb', command = cmd, active = True,
  928. name = name, hidden = False, opacity = 1.0, render = True)
  929. self.frame.Render(self.mapWindow)
  930. self.layerName[name] = name
  931. self.toolbar.choice.Insert(name, 0)
  932. self.toolbar.choice.SetSelection(0)
  933. def RemoveTemporaryLayer(self, name):
  934. """!Removes temporary layer (if exists) from Map and and updates toolbar.
  935. @param name real name of layer
  936. """
  937. # check if layer is loaded
  938. layers = self.map.GetListOfLayers(ltype = 'raster')
  939. idx = None
  940. for i, layer in enumerate(layers):
  941. if name == layer.GetName():
  942. idx = i
  943. break
  944. if idx is None:
  945. return
  946. # remove it from Map
  947. self.map.RemoveLayer(name = name)
  948. # update inner list of layers
  949. alias = self.GetAlias(name)
  950. if alias not in self.layerName:
  951. return
  952. del self.layerName[alias]
  953. # update choice
  954. idx = self.toolbar.choice.FindString(alias)
  955. if idx != wx.NOT_FOUND:
  956. self.toolbar.choice.Delete(idx)
  957. if not self.toolbar.choice.IsEmpty():
  958. self.toolbar.choice.SetSelection(0)
  959. self.frame.Render(self.mapWindow)
  960. def RemoveLayer(self, name, idx):
  961. """!Removes layer from Map and update toolbar"""
  962. name = self.layerName[name]
  963. self.map.RemoveLayer(name = name)
  964. del self.layerName[name]
  965. self.toolbar.choice.Delete(idx)
  966. if not self.toolbar.choice.IsEmpty():
  967. self.toolbar.choice.SetSelection(0)
  968. self.frame.Render(self.mapWindow)
  969. def SelectLayer(self, name):
  970. """!Moves selected layer to top"""
  971. layers = self.map.GetListOfLayers(ltype = 'raster') + \
  972. self.map.GetListOfLayers(ltype = 'rgb')
  973. idx = None
  974. for i, layer in enumerate(layers):
  975. if self.layerName[name] == layer.GetName():
  976. idx = i
  977. break
  978. if idx is not None: # should not happen
  979. layers.append(layers.pop(idx))
  980. choice = self.toolbar.choice
  981. idx = choice.FindString(name)
  982. choice.Delete(idx)
  983. choice.Insert(name, 0)
  984. choice.SetSelection(0)
  985. #layers.reverse()
  986. self.map.ReorderLayers(layers)
  987. self.frame.Render(self.mapWindow)
  988. def SetOpacity(self, name):
  989. """!Sets opacity of layers."""
  990. name = self.layerName[name]
  991. layers = self.map.GetListOfLayers(name = name)
  992. if not layers:
  993. return
  994. # works for first layer only
  995. oldOpacity = layers[0].GetOpacity()
  996. dlg = SetOpacityDialog(self.frame, opacity = oldOpacity)
  997. if dlg.ShowModal() == wx.ID_OK:
  998. self.map.ChangeOpacity(layer = layers[0], opacity = dlg.GetOpacity())
  999. dlg.Destroy()
  1000. self.frame.Render(self.mapWindow)
  1001. def _addSuffix(self, name):
  1002. suffix = _('results')
  1003. return '_'.join((name, suffix))
  1004. def GetAlias(self, name):
  1005. """!Returns alias for layer"""
  1006. name = [k for k, v in self.layerName.iteritems() if v == name]
  1007. if name:
  1008. return name[0]
  1009. return None
  1010. def SetAlias(self, original, alias):
  1011. name = self.GetAlias(original)
  1012. if name:
  1013. self.layerName[self._addSuffix(alias)] = original
  1014. del self.layerName[name]
  1015. idx = self.toolbar.choice.FindString(name)
  1016. if idx != wx.NOT_FOUND:
  1017. self.toolbar.choice.SetString(idx, self._addSuffix(alias))
  1018. def test():
  1019. import gettext
  1020. import core.render as render
  1021. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  1022. app = wx.PySimpleApp()
  1023. wx.InitAllImageHandlers()
  1024. frame = IClassMapFrame()
  1025. frame.Show()
  1026. app.MainLoop()
  1027. if __name__ == "__main__":
  1028. test()