frame.py 50 KB

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