controllers.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  1. """
  2. @package iscatt.controllers
  3. @brief Controller layer wx.iscatt.
  4. Classes:
  5. - controllers::ScattsManager
  6. - controllers::PlotsRenderingManager
  7. - controllers::CategoriesManager
  8. - controllers::IMapWinDigitConnection
  9. - controllers::IClassDigitConnection
  10. - controllers::IMapDispConnection
  11. - controllers::IClassConnection
  12. (C) 2013 by the GRASS Development Team
  13. This program is free software under the GNU General Public License
  14. (>=v2). Read the file COPYING that comes with GRASS for details.
  15. @author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
  16. """
  17. import os
  18. import sys
  19. from copy import deepcopy
  20. import wx
  21. from core.gcmd import GException, GError, GMessage, RunCommand, GWarning
  22. from core.settings import UserSettings
  23. from core.gthread import gThread
  24. from iscatt.iscatt_core import Core, idBandsToidScatt, GetRasterInfo, GetRegion, \
  25. MAX_SCATT_SIZE, WARN_SCATT_SIZE, MAX_NCELLS, WARN_NCELLS
  26. from iscatt.dialogs import AddScattPlotDialog, ExportCategoryRaster
  27. from iclass.dialogs import IClassGroupDialog
  28. import grass.script as grass
  29. from grass.pydispatch.signal import Signal
  30. class ScattsManager:
  31. """Main controller
  32. """
  33. def __init__(self, guiparent, giface, iclass_mapwin=None):
  34. self.giface = giface
  35. self.mapDisp = giface.GetMapDisplay()
  36. if iclass_mapwin:
  37. self.mapWin = iclass_mapwin
  38. else:
  39. self.mapWin = giface.GetMapWindow()
  40. self.guiparent = guiparent
  41. self.show_add_scatt_plot = False
  42. self.core = Core()
  43. self.cats_mgr = CategoriesManager(self, self.core)
  44. self.render_mgr = PlotsRenderingManager(scatt_mgr=self,
  45. cats_mgr=self.cats_mgr,
  46. core=self.core)
  47. self.thread = gThread()
  48. self.plots = {}
  49. self.plot_mode = None
  50. self.pol_sel_mode = [False, None]
  51. self.data_set = False
  52. self.cursorPlotMove = Signal("ScattsManager.cursorPlotMove")
  53. self.renderingStarted = self.render_mgr.renderingStarted
  54. self.renderingFinished = self.render_mgr.renderingFinished
  55. self.computingStarted = Signal("ScattsManager.computingStarted")
  56. if iclass_mapwin:
  57. self.digit_conn = IClassDigitConnection(self,
  58. self.mapWin,
  59. self.core.CatRastUpdater())
  60. self.iclass_conn = IClassConnection(self,
  61. iclass_mapwin.parent,
  62. self.cats_mgr)
  63. else:
  64. self.digit_conn = IMapWinDigitConnection()
  65. self.iclass_conn = IMapDispConnection(scatt_mgr=self,
  66. cats_mgr=self.cats_mgr,
  67. giface=self.giface)
  68. self._initSettings()
  69. self.modeSet = Signal("ScattsManager.mondeSet")
  70. def CleanUp(self):
  71. self.thread.Terminate()
  72. # there should be better way hot to clean up the thread
  73. # than calling the clean up function outside the thread,
  74. # which still may running
  75. self.core.CleanUp()
  76. def CleanUpDone(self):
  77. for scatt_id, scatt in self.plots.items():
  78. if scatt['scatt']:
  79. scatt['scatt'].CleanUp()
  80. self.plots.clear()
  81. def _initSettings(self):
  82. """Initialization of settings (if not already defined)
  83. """
  84. # initializes default settings
  85. initSettings = [
  86. ['selection', 'sel_pol', (255, 255, 0)],
  87. ['selection', 'sel_pol_vertex', (255, 0, 0)],
  88. ['selection', 'sel_area', (0, 255, 19)],
  89. ['selection', "snap_tresh", 10],
  90. ['selection', 'sel_area_opacty', 50],
  91. ['ellipses', 'show_ellips', True],
  92. ]
  93. for init in initSettings:
  94. UserSettings.ReadSettingsFile()
  95. UserSettings.Append(dict=UserSettings.userSettings,
  96. group='scatt',
  97. key=init[0],
  98. subkey=init[1],
  99. value=init[2],
  100. overwrite=False)
  101. def SetData(self):
  102. self.iclass_conn.SetData()
  103. self.digit_conn.SetData()
  104. def SetBands(self, bands):
  105. self.busy = wx.BusyInfo(_("Loading data..."))
  106. self.data_set = False
  107. self.thread.Run(callable=self.core.CleanUp,
  108. ondone=lambda event: self.CleanUpDone())
  109. if self.show_add_scatt_plot:
  110. show_add = True
  111. else:
  112. show_add = False
  113. self.all_bands_to_bands = dict(zip(bands, [-1] * len(bands)))
  114. self.all_bands = bands
  115. self.region = GetRegion()
  116. ncells = self.region["rows"] * self.region["cols"]
  117. if ncells > MAX_NCELLS:
  118. del self.busy
  119. self.data_set = True
  120. return
  121. self.bands = bands[:]
  122. self.bands_info = {}
  123. valid_bands = []
  124. for b in self.bands[:]:
  125. i = GetRasterInfo(b)
  126. self.bands_info[b] = i
  127. if i is not None:
  128. valid_bands.append(b)
  129. for i, b in enumerate(valid_bands):
  130. # name : index in core bands -
  131. # if not in core bands (not CELL type) -> index = -1
  132. self.all_bands_to_bands[b] = i
  133. self.thread.Run(callable=self.core.SetData,
  134. bands=valid_bands,
  135. ondone=self.SetDataDone,
  136. userdata={"show_add": show_add})
  137. def SetDataDone(self, event):
  138. del self.busy
  139. self.data_set = True
  140. todo = event.ret
  141. self.bad_bands = event.ret
  142. bands = self.core.GetBands()
  143. self.bad_rasts = event.ret
  144. self.cats_mgr.SetData()
  145. if event.userdata['show_add']:
  146. self.AddScattPlot()
  147. def GetBands(self):
  148. return self.core.GetBands()
  149. def AddScattPlot(self):
  150. if not self.data_set and self.iclass_conn:
  151. self.show_add_scatt_plot = True
  152. self.iclass_conn.SetData()
  153. self.show_add_scatt_plot = False
  154. return
  155. if not self.data_set:
  156. GError(_('No data set.'))
  157. return
  158. self.computingStarted.emit()
  159. bands = self.core.GetBands()
  160. #added_bands_ids = []
  161. # for scatt_id in self.plots):
  162. # added_bands_ids.append[idBandsToidScatt(scatt_id)]
  163. self.digit_conn.Update()
  164. ncells = self.region["rows"] * self.region["cols"]
  165. if ncells > MAX_NCELLS:
  166. GError(
  167. _(
  168. parent=self.guiparent, mmessage=_(
  169. "Interactive Scatter Plot Tool can not be used.\n"
  170. "Number of cells (rows*cols) <%d> in current region"
  171. "is higher than maximum limit <%d>.\n\n"
  172. "You can reduce number of cells in current region using <g.region> command." %
  173. (ncells, MAX_NCELLS))))
  174. return
  175. elif ncells > WARN_NCELLS:
  176. dlg = wx.MessageDialog(
  177. parent=self.guiparent,
  178. message=_("Number of cells (rows*cols) <%d> in current region is "
  179. "higher than recommended threshold <%d>.\n"
  180. "It is strongly advised to reduce number of cells "
  181. "in current region below recommend threshold.\n "
  182. "It can be done by <g.region> command.\n\n"
  183. "Do you want to continue using "
  184. "Interactive Scatter Plot Tool with this region?"
  185. % (ncells, WARN_NCELLS)),
  186. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING)
  187. ret = dlg.ShowModal()
  188. if ret != wx.ID_YES:
  189. return
  190. dlg = AddScattPlotDialog(parent=self.guiparent,
  191. bands=self.all_bands,
  192. check_bands_callback=self.CheckBands)
  193. if dlg.ShowModal() == wx.ID_OK:
  194. scatt_ids = []
  195. sel_bands = dlg.GetBands()
  196. for b_1, b_2 in sel_bands:
  197. transpose = False
  198. if b_1 > b_2:
  199. transpose = True
  200. tmp_band = b_2
  201. b_2 = b_1
  202. b_1 = tmp_band
  203. b_1_id = self.all_bands_to_bands[self.all_bands[b_1]]
  204. b_2_id = self.all_bands_to_bands[self.all_bands[b_2]]
  205. scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands))
  206. if scatt_id in self.plots:
  207. continue
  208. self.plots[scatt_id] = {'transpose': transpose,
  209. 'scatt': None}
  210. scatt_ids.append(scatt_id)
  211. self._addScattPlot(scatt_ids)
  212. dlg.Destroy()
  213. def CheckBands(self, b_1, b_2):
  214. bands = self.core.GetBands()
  215. added_scatts_ids = self.plots.keys()
  216. b_1_id = self.all_bands_to_bands[self.all_bands[b_1]]
  217. b_2_id = self.all_bands_to_bands[self.all_bands[b_1]]
  218. scatt_id = idBandsToidScatt(b_1_id, b_2_id, len(bands))
  219. if scatt_id in added_scatts_ids:
  220. GWarning(
  221. parent=self.guiparent, message=_(
  222. "Scatter plot with same band combination (regardless x y order) "
  223. "is already displayed."))
  224. return False
  225. b_1_name = self.all_bands[b_1]
  226. b_2_name = self.all_bands[b_2]
  227. b_1_i = self.bands_info[b_1_name]
  228. b_2_i = self.bands_info[b_2_name]
  229. err = ""
  230. for b in [b_1_name, b_2_name]:
  231. if self.bands_info[b] is None:
  232. err += _("Band <%s> is not CELL (integer) type.\n" % b)
  233. if err:
  234. GMessage(parent=self.guiparent,
  235. message=_("Scatter plot cannot be added.\n" + err))
  236. return False
  237. mrange = b_1_i['range'] * b_2_i['range']
  238. if mrange > MAX_SCATT_SIZE:
  239. GWarning(parent=self.guiparent,
  240. message=_("Scatter plot cannot be added.\n"
  241. "Multiple of bands ranges <%s:%d * %s:%d = %d> "
  242. "is higher than maximum limit <%d>.\n"
  243. % (b_1_name, b_1_i['range'], b_1_name, b_2_i['range'],
  244. mrange, MAX_SCATT_SIZE)))
  245. return False
  246. elif mrange > WARN_SCATT_SIZE:
  247. dlg = wx.MessageDialog(
  248. parent=self.guiparent,
  249. message=_(
  250. "Multiple of bands ranges <%s:%d * %s:%d = %d> "
  251. "is higher than recommended limit <%d>.\n"
  252. "It is strongly advised to reduce range extend of bands"
  253. "(e. g. using r.rescale) below recommended threshold.\n\n"
  254. "Do you really want to add this scatter plot?" %
  255. (b_1_name, b_1_i['range'],
  256. b_1_name, b_2_i['range'],
  257. mrange, WARN_SCATT_SIZE)),
  258. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING)
  259. ret = dlg.ShowModal()
  260. if ret != wx.ID_YES:
  261. return False
  262. return True
  263. def _addScattPlot(self, scatt_ids):
  264. self.render_mgr.NewRunningProcess()
  265. self.thread.Run(callable=self.core.AddScattPlots,
  266. scatt_ids=scatt_ids, ondone=self.AddScattPlotDone)
  267. def AddScattPlotDone(self, event):
  268. if not self.data_set:
  269. return
  270. scatt_ids = event.kwds['scatt_ids']
  271. for s_id in scatt_ids:
  272. trans = self.plots[s_id]['transpose']
  273. self.plots[s_id]['scatt'] = self.guiparent.NewScatterPlot(
  274. scatt_id=s_id, transpose=trans)
  275. self.plots[s_id]['scatt'].plotClosed.connect(self.PlotClosed)
  276. self.plots[s_id]['scatt'].cursorMove.connect(
  277. lambda x, y, scatt_id:
  278. self.cursorPlotMove.emit(x=x, y=y,
  279. scatt_id=scatt_id))
  280. if self.plot_mode:
  281. self.plots[s_id]['scatt'].SetMode(self.plot_mode)
  282. self.plots[s_id]['scatt'].ZoomToExtend()
  283. self.render_mgr.RunningProcessDone()
  284. def PlotClosed(self, scatt_id):
  285. del self.plots[scatt_id]
  286. def SetPlotsMode(self, mode):
  287. self.plot_mode = mode
  288. for scatt in self.plots.itervalues():
  289. if scatt['scatt']:
  290. scatt['scatt'].SetMode(mode)
  291. self.modeSet.emit(mode=mode)
  292. def ActivateSelectionPolygonMode(self, activate):
  293. self.pol_sel_mode[0] = activate
  294. for scatt in self.plots.itervalues():
  295. if not scatt['scatt']:
  296. continue
  297. scatt['scatt'].SetSelectionPolygonMode(activate)
  298. if not activate and self.plot_mode not in [
  299. 'zoom', 'pan', 'zoom_extend']:
  300. self.SetPlotsMode(None)
  301. self.render_mgr.RunningProcessDone()
  302. return activate
  303. def ProcessSelectionPolygons(self, process_mode):
  304. scatts_polygons = {}
  305. for scatt_id, scatt in self.plots.iteritems():
  306. if not scatt['scatt']:
  307. continue
  308. coords = scatt['scatt'].GetCoords()
  309. if coords is not None:
  310. scatts_polygons[scatt_id] = coords
  311. if not scatts_polygons:
  312. return
  313. value = 1
  314. if process_mode == 'remove':
  315. value = 0
  316. sel_cat_id = self.cats_mgr.GetSelectedCat()
  317. if not sel_cat_id:
  318. dlg = wx.MessageDialog(
  319. parent=self.guiparent,
  320. message=_(
  321. "In order to select arrea in scatter plot, "
  322. "you have to select class first.\n\n"
  323. "There is no class yet, "
  324. "do you want to create one?"),
  325. caption=_("No class selected"),
  326. style=wx.YES_NO)
  327. if dlg.ShowModal() == wx.ID_YES:
  328. self.iclass_conn.EmptyCategories()
  329. sel_cat_id = self.cats_mgr.GetSelectedCat()
  330. if not sel_cat_id:
  331. return
  332. for scatt in self.plots.itervalues():
  333. if scatt['scatt']:
  334. scatt['scatt'].SetEmpty()
  335. self.computingStarted.emit()
  336. self.render_mgr.NewRunningProcess()
  337. self.render_mgr.CategoryChanged(cat_ids=[sel_cat_id])
  338. self.render_mgr.CategoryCondsChanged(cat_ids=[sel_cat_id])
  339. self.thread.Run(callable=self.core.UpdateCategoryWithPolygons,
  340. cat_id=sel_cat_id,
  341. scatts_pols=scatts_polygons,
  342. value=value, ondone=self.SetEditCatDataDone)
  343. def SetEditCatDataDone(self, event):
  344. if not self.data_set:
  345. return
  346. self.render_mgr.RunningProcessDone()
  347. if event.exception:
  348. GError(
  349. _("Error occurred during computation of scatter plot category:\n%s"),
  350. parent=self.guiparent,
  351. showTraceback=False)
  352. cat_id = event.ret
  353. self.iclass_conn.RenderCatRast(cat_id)
  354. def SettingsUpdated(self, chanaged_setts):
  355. self.render_mgr.RenderRequest()
  356. #['ellipses', 'show_ellips']
  357. def GetCategoriesManager(self):
  358. return self.cats_mgr
  359. class PlotsRenderingManager:
  360. """Manages rendering of scatter plot.
  361. .. todo::
  362. still space for optimalization
  363. """
  364. def __init__(self, scatt_mgr, cats_mgr, core):
  365. self.scatt_mgr = scatt_mgr
  366. self.cats_mgr = cats_mgr
  367. self.core = core
  368. self.scatts_dt, self.scatt_conds_dt = self.core.GetScattsData()
  369. self.runningProcesses = 0
  370. self.data_to_render = {}
  371. self.render_queue = []
  372. self.cat_ids = []
  373. self.cat_cond_ids = []
  374. self.renderingStarted = Signal("ScattsManager.renderingStarted")
  375. self.renderingFinished = Signal("ScattsManager.renderingFinished")
  376. def AddRenderRequest(self, scatts):
  377. for scatt_id, cat_ids in scatts:
  378. if not self.data_to_render.has_key[scatt_id]:
  379. self.data_to_render = cat_ids
  380. else:
  381. for c in cat_ids:
  382. if c not in self.data_to_render[scatt_id]:
  383. self.data_to_render[scatt_id].append(c)
  384. def NewRunningProcess(self):
  385. self.runningProcesses += 1
  386. def RunningProcessDone(self):
  387. self.runningProcesses -= 1
  388. if self.runningProcesses <= 1:
  389. self.RenderScattPlts()
  390. def RenderRequest(self):
  391. if self.runningProcesses <= 1:
  392. self.RenderScattPlts()
  393. def CategoryChanged(self, cat_ids):
  394. for c in cat_ids:
  395. if c not in self.cat_ids:
  396. self.cat_ids.append(c)
  397. def CategoryCondsChanged(self, cat_ids):
  398. for c in cat_ids:
  399. if c not in self.cat_cond_ids:
  400. self.cat_cond_ids.append(c)
  401. def RenderScattPlts(self, scatt_ids=None):
  402. if len(self.render_queue) > 1:
  403. return
  404. self.renderingStarted.emit()
  405. self.render_queue.append(self.scatt_mgr.thread.GetId())
  406. cats_attrs = deepcopy(self.cats_mgr.GetCategoriesAttrs())
  407. cats = self.cats_mgr.GetCategories()[:]
  408. self.scatt_mgr.thread.Run(
  409. callable=self._renderscattplts,
  410. scatt_ids=scatt_ids,
  411. cats=cats,
  412. cats_attrs=cats_attrs,
  413. ondone=self.RenderingDone)
  414. def _renderscattplts(self, scatt_ids, cats, cats_attrs):
  415. cats.reverse()
  416. cats.insert(0, 0)
  417. for i_scatt_id, scatt in self.scatt_mgr.plots.items():
  418. if scatt_ids is not None and \
  419. i_scatt_id not in scatt_ids:
  420. continue
  421. if not scatt['scatt']:
  422. continue
  423. scatt_dt = self.scatts_dt.GetScatt(i_scatt_id)
  424. if self._showConfEllipses():
  425. ellipses_dt = self.scatts_dt.GetEllipses(
  426. i_scatt_id, cats_attrs)
  427. else:
  428. ellipses_dt = {}
  429. for c in scatt_dt.iterkeys():
  430. try:
  431. self.cat_ids.remove(c)
  432. scatt_dt[c]['render'] = True
  433. except:
  434. scatt_dt[c]['render'] = False
  435. if self.scatt_mgr.pol_sel_mode[0]:
  436. self._getSelectedAreas(cats, i_scatt_id, scatt_dt, cats_attrs)
  437. scatt['scatt'].Plot(cats_order=cats,
  438. scatts=scatt_dt,
  439. ellipses=ellipses_dt,
  440. styles=cats_attrs)
  441. def RenderingDone(self, event):
  442. self.render_queue.remove(event.pid)
  443. if not self.render_queue:
  444. self.renderingFinished.emit()
  445. def _getSelectedAreas(self, cats_order, scatt_id, scatt_dt, cats_attrs):
  446. cat_id = self.cats_mgr.GetSelectedCat()
  447. if not cat_id:
  448. return
  449. sel_a_cat_id = -1
  450. s = self.scatt_conds_dt.GetScatt(scatt_id, [cat_id])
  451. if not s:
  452. return
  453. cats_order.append(sel_a_cat_id)
  454. col = UserSettings.Get(group='scatt',
  455. key='selection',
  456. subkey='sel_area')
  457. col = ":".join(map(str, col))
  458. opac = UserSettings.Get(group='scatt',
  459. key='selection',
  460. subkey='sel_area_opacty') / 100.0
  461. cats_attrs[sel_a_cat_id] = {'color': col,
  462. 'opacity': opac,
  463. 'show': True}
  464. scatt_dt[sel_a_cat_id] = s[cat_id]
  465. scatt_dt[sel_a_cat_id]['render'] = False
  466. if cat_id in self.cat_cond_ids:
  467. scatt_dt[sel_a_cat_id]['render'] = True
  468. self.cat_cond_ids.remove(cat_id)
  469. def _showConfEllipses(self):
  470. return UserSettings.Get(group='scatt',
  471. key="ellipses",
  472. subkey="show_ellips")
  473. class CategoriesManager:
  474. """Manages categories list of scatter plot.
  475. """
  476. def __init__(self, scatt_mgr, core):
  477. self.core = core
  478. self.scatt_mgr = scatt_mgr
  479. self.cats = {}
  480. self.cats_ids = []
  481. self.sel_cat_id = None
  482. self.exportRaster = None
  483. self.initialized = Signal('CategoriesManager.initialized')
  484. self.setCategoryAttrs = Signal('CategoriesManager.setCategoryAttrs')
  485. self.deletedCategory = Signal('CategoriesManager.deletedCategory')
  486. self.addedCategory = Signal('CategoriesManager.addedCategory')
  487. def ChangePosition(self, cat_id, new_pos):
  488. if new_pos >= len(self.cats_ids):
  489. return False
  490. try:
  491. pos = self.cats_ids.index(cat_id)
  492. except:
  493. return False
  494. if pos > new_pos:
  495. pos -= 1
  496. self.cats_ids.remove(cat_id)
  497. self.cats_ids.insert(new_pos, cat_id)
  498. self.scatt_mgr.render_mgr.RenderRequest()
  499. return True
  500. def _addCategory(self, cat_id):
  501. self.scatt_mgr.thread.Run(callable=self.core.AddCategory,
  502. cat_id=cat_id)
  503. def SetData(self):
  504. if not self.scatt_mgr.data_set:
  505. return
  506. for cat_id in self.cats_ids:
  507. self.scatt_mgr.thread.Run(callable=self.core.AddCategory,
  508. cat_id=cat_id)
  509. def AddCategory(self, cat_id=None, name=None, color=None, nstd=None):
  510. if cat_id is None:
  511. if self.cats_ids:
  512. cat_id = max(self.cats_ids) + 1
  513. else:
  514. cat_id = 1
  515. if self.scatt_mgr.data_set:
  516. self.scatt_mgr.thread.Run(callable=self.core.AddCategory,
  517. cat_id=cat_id)
  518. # TODO check number of cats
  519. # if ret < 0: #TODO
  520. # return -1;
  521. self.cats[cat_id] = {
  522. 'name': 'class_%d' % cat_id,
  523. 'color': "0:0:0",
  524. 'opacity': 1.0,
  525. 'show': True,
  526. 'nstd': 1.0,
  527. }
  528. self.cats_ids.insert(0, cat_id)
  529. if name is not None:
  530. self.cats[cat_id]["name"] = name
  531. if color is not None:
  532. self.cats[cat_id]["color"] = color
  533. if nstd is not None:
  534. self.cats[cat_id]["nstd"] = nstd
  535. self.addedCategory.emit(cat_id=cat_id,
  536. name=self.cats[cat_id]["name"],
  537. color=self.cats[cat_id]["color"])
  538. return cat_id
  539. def SetCategoryAttrs(self, cat_id, attrs_dict):
  540. render = False
  541. update_cat_rast = []
  542. for k, v in attrs_dict.iteritems():
  543. if not render and k in ['color', 'opacity', 'show', 'nstd']:
  544. render = True
  545. if k in ['color', 'name']:
  546. update_cat_rast.append(k)
  547. self.cats[cat_id][k] = v
  548. if render:
  549. self.scatt_mgr.render_mgr.CategoryChanged(cat_ids=[cat_id])
  550. self.scatt_mgr.render_mgr.RenderRequest()
  551. if update_cat_rast:
  552. self.scatt_mgr.iclass_conn.UpdateCategoryRaster(
  553. cat_id, update_cat_rast)
  554. self.setCategoryAttrs.emit(cat_id=cat_id, attrs_dict=attrs_dict)
  555. def DeleteCategory(self, cat_id):
  556. if self.scatt_mgr.data_set:
  557. self.scatt_mgr.thread.Run(callable=self.core.DeleteCategory,
  558. cat_id=cat_id)
  559. del self.cats[cat_id]
  560. self.cats_ids.remove(cat_id)
  561. self.deletedCategory.emit(cat_id=cat_id)
  562. # TODO emit event?
  563. def SetSelectedCat(self, cat_id):
  564. self.sel_cat_id = cat_id
  565. if self.scatt_mgr.pol_sel_mode[0]:
  566. self.scatt_mgr.render_mgr.RenderRequest()
  567. def GetSelectedCat(self):
  568. return self.sel_cat_id
  569. def GetCategoryAttrs(self, cat_id):
  570. #TODO is mutable
  571. return self.cats[cat_id]
  572. def GetCategoriesAttrs(self):
  573. #TODO is mutable
  574. return self.cats
  575. def GetCategories(self):
  576. return self.cats_ids[:]
  577. def SetCategoryPosition(self):
  578. if newindex > oldindex:
  579. newindex -= 1
  580. self.cats_ids.insert(newindex, self.cats_ids.pop(oldindex))
  581. def ExportCatRast(self, cat_id):
  582. cat_attrs = self.GetCategoryAttrs(cat_id)
  583. dlg = ExportCategoryRaster(
  584. parent=self.scatt_mgr.guiparent,
  585. rasterName=self.exportRaster,
  586. title=_("Export scatter plot raster of class <%s>") %
  587. cat_attrs['name'])
  588. if dlg.ShowModal() == wx.ID_OK:
  589. self.exportCatRast = dlg.GetRasterName()
  590. dlg.Destroy()
  591. self.scatt_mgr.thread.Run(callable=self.core.ExportCatRast,
  592. userdata={'name': cat_attrs['name']},
  593. cat_id=cat_id,
  594. rast_name=self.exportCatRast,
  595. ondone=self.OnExportCatRastDone)
  596. def OnExportCatRastDone(self, event):
  597. ret, err = event.ret
  598. if ret == 0:
  599. cat_attrs = self.GetCategoryAttrs(event.kwds['cat_id'])
  600. GMessage(
  601. _("Scatter plot raster of class <%s> exported to raster map <%s>.") %
  602. (event.userdata['name'], event.kwds['rast_name']))
  603. else:
  604. GMessage(
  605. _("Export of scatter plot raster of class <%s> to map <%s> failed.\n%s") %
  606. (event.userdata['name'], event.kwds['rast_name'], err))
  607. class IMapWinDigitConnection:
  608. """Manage communication of the scatter plot with digitizer in
  609. mapwindow (does not work).
  610. """
  611. def Update(self):
  612. pass
  613. def SetData(self):
  614. pass
  615. class IClassDigitConnection:
  616. """Manages communication of the scatter plot with digitizer in
  617. wx.iclass.
  618. """
  619. def __init__(self, scatt_mgr, mapWin, scatt_rast_updater):
  620. self.mapWin = mapWin
  621. self.vectMap = None
  622. self.scatt_rast_updater = scatt_rast_updater
  623. self.scatt_mgr = scatt_mgr
  624. self.cats_mgr = scatt_mgr.cats_mgr
  625. self.cats_to_update = []
  626. self.pids = {'mapwin_conn': []}
  627. self.thread = self.scatt_mgr.thread
  628. # TODO
  629. self.mapWin.parent.toolbars[
  630. "vdigit"].editingStarted.connect(self.DigitDataChanged)
  631. def Update(self):
  632. self.thread.Run(callable=self.scatt_rast_updater.SyncWithMap)
  633. def SetData(self):
  634. self.cats_to_update = []
  635. self.pids = {'mapwin_conn': []}
  636. def _connectSignals(self):
  637. self.digit.featureAdded.connect(self.AddFeature)
  638. self.digit.areasDeleted.connect(self.DeleteAreas)
  639. self.digit.featuresDeleted.connect(self.DeleteAreas)
  640. self.digit.vertexMoved.connect(self.EditedFeature)
  641. self.digit.vertexRemoved.connect(self.EditedFeature)
  642. self.digit.lineEdited.connect(self.EditedFeature)
  643. self.digit.featuresMoved.connect(self.EditedFeature)
  644. def AddFeature(self, new_bboxs, new_areas_cats):
  645. if not self.scatt_mgr.data_set:
  646. return
  647. self.scatt_mgr.computingStarted.emit()
  648. self.pids['mapwin_conn'].append(self.thread.GetId())
  649. self.thread.Run(callable=self.scatt_rast_updater.EditedFeature,
  650. new_bboxs=new_bboxs,
  651. old_bboxs=[],
  652. old_areas_cats=[],
  653. new_areas_cats=new_areas_cats,
  654. ondone=self.OnDone)
  655. def DeleteAreas(self, old_bboxs, old_areas_cats):
  656. if not self.scatt_mgr.data_set:
  657. return
  658. self.scatt_mgr.computingStarted.emit()
  659. self.pids['mapwin_conn'].append(self.thread.GetId())
  660. self.thread.Run(callable=self.scatt_rast_updater.EditedFeature,
  661. new_bboxs=[],
  662. old_bboxs=old_bboxs,
  663. old_areas_cats=old_areas_cats,
  664. new_areas_cats=[],
  665. ondone=self.OnDone)
  666. def EditedFeature(self, new_bboxs, new_areas_cats,
  667. old_bboxs, old_areas_cats):
  668. if not self.scatt_mgr.data_set:
  669. return
  670. self.scatt_mgr.computingStarted.emit()
  671. self.pids['mapwin_conn'].append(self.thread.GetId())
  672. self.thread.Run(callable=self.scatt_rast_updater.EditedFeature,
  673. new_bboxs=new_bboxs,
  674. old_bboxs=old_bboxs,
  675. old_areas_cats=old_areas_cats,
  676. new_areas_cats=new_areas_cats,
  677. ondone=self.OnDone)
  678. def DigitDataChanged(self, vectMap, digit):
  679. self.digit = digit
  680. self.vectMap = vectMap
  681. self.digit.EmitSignals(emit=True)
  682. self.scatt_rast_updater.SetVectMap(vectMap)
  683. self._connectSignals()
  684. def OnDone(self, event):
  685. if not self.scatt_mgr.data_set:
  686. return
  687. self.pids['mapwin_conn'].remove(event.pid)
  688. updated_cats = event.ret
  689. for cat in updated_cats:
  690. if cat not in self.cats_to_update:
  691. self.cats_to_update.append(cat)
  692. if not self.pids['mapwin_conn']:
  693. self.thread.Run(
  694. callable=self.scatt_mgr.core.ComputeCatsScatts,
  695. cats_ids=self.cats_to_update[:],
  696. ondone=self.Render)
  697. del self.cats_to_update[:]
  698. def Render(self, event):
  699. self.scatt_mgr.render_mgr.RenderScattPlts()
  700. class IMapDispConnection:
  701. """Manage comunication of the scatter plot with mapdisplay in mapwindow.
  702. """
  703. def __init__(self, scatt_mgr, cats_mgr, giface):
  704. self.scatt_mgr = scatt_mgr
  705. self.cats_mgr = cats_mgr
  706. self.set_g = {'group': None, 'subg': None}
  707. self.giface = giface
  708. self.added_cats_rasts = {}
  709. def SetData(self):
  710. dlg = IClassGroupDialog(self.scatt_mgr.guiparent,
  711. group=self.set_g['group'],
  712. subgroup=self.set_g['subg'])
  713. bands = []
  714. while True:
  715. if dlg.ShowModal() == wx.ID_OK:
  716. bands = dlg.GetGroupBandsErr(parent=self.scatt_mgr.guiparent)
  717. if bands:
  718. name, s = dlg.GetData()
  719. group = grass.find_file(name=name, element='group')
  720. self.set_g['group'] = group['name']
  721. self.set_g['subg'] = s
  722. break
  723. else:
  724. break
  725. dlg.Destroy()
  726. self.added_cats_rasts = {}
  727. if bands:
  728. self.scatt_mgr.SetBands(bands)
  729. def EmptyCategories(self):
  730. return None
  731. def UpdateCategoryRaster(self, cat_id, attrs, render=True):
  732. cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
  733. if not grass.find_file(cat_rast, element='cell', mapset='.')['file']:
  734. return
  735. cats_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
  736. if "color" in attrs:
  737. ret, err_msg = RunCommand('r.colors',
  738. map=cat_rast,
  739. rules="-",
  740. stdin="1 %s" % cats_attrs["color"],
  741. getErrorMsg=True)
  742. if ret != 0:
  743. GError("r.colors failed\n%s" % err_msg)
  744. if render:
  745. self.giface.updateMap.emit()
  746. if "name" in attrs:
  747. # TODO hack
  748. self.giface.GetLayerList()._tree.SetItemText(
  749. self.added_cats_rasts[cat_id], cats_attrs['name'])
  750. cats_attrs["name"]
  751. def RenderCatRast(self, cat_id):
  752. if not cat_id in self.added_cats_rasts.iterkeys():
  753. cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
  754. cat_name = self.cats_mgr.GetCategoryAttrs(cat_id)['name']
  755. self.UpdateCategoryRaster(cat_id, ['color'], render=False)
  756. cmd = ['d.rast', 'map=%s' % cat_rast]
  757. # TODO HACK
  758. layer = self.giface.GetLayerList()._tree.AddLayer(ltype="raster",
  759. lname=cat_name,
  760. lcmd=cmd,
  761. lchecked=True)
  762. self.added_cats_rasts[cat_id] = layer
  763. else: # TODO settings
  764. self.giface.updateMap.emit()
  765. class IClassConnection:
  766. """Manage comunication of the scatter plot with mapdisplay in wx.iclass.
  767. """
  768. def __init__(self, scatt_mgr, iclass_frame, cats_mgr):
  769. self.iclass_frame = iclass_frame
  770. self.stats_data = self.iclass_frame.stats_data
  771. self.cats_mgr = cats_mgr
  772. self.scatt_mgr = scatt_mgr
  773. self.added_cats_rasts = []
  774. self.stats_data.statisticsAdded.connect(self.AddCategory)
  775. self.stats_data.statisticsDeleted.connect(self.DeleteCategory)
  776. self.stats_data.allStatisticsDeleted.connect(self.DeletAllCategories)
  777. self.stats_data.statisticsSet.connect(self.SetCategory)
  778. self.iclass_frame.groupSet.connect(self.GroupSet)
  779. self.cats_mgr.setCategoryAttrs.connect(self.SetStatistics)
  780. self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
  781. self.cats_mgr.addedCategory.connect(self.AddStatistics)
  782. self.iclass_frame.categoryChanged.connect(self.CategoryChanged)
  783. self.SyncCats()
  784. def UpdateCategoryRaster(self, cat_id, attrs, render=True):
  785. if not self.scatt_mgr.data_set:
  786. return
  787. cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
  788. if not cat_rast:
  789. return
  790. if not grass.find_file(cat_rast, element='cell', mapset='.')['file']:
  791. return
  792. cats_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
  793. train_mgr, preview_mgr = self.iclass_frame.GetMapManagers()
  794. if "color" in attrs:
  795. ret, err_msg = RunCommand('r.colors',
  796. map=cat_rast,
  797. rules="-",
  798. stdin="1 %s" % cats_attrs["color"],
  799. getErrorMsg=True)
  800. if ret != 0:
  801. GError("r.colors failed\n%s" % err_msg)
  802. if render:
  803. train_mgr.Render()
  804. if "name" in attrs:
  805. cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
  806. train_mgr.SetAlias(original=cat_rast, alias=cats_attrs['name'])
  807. cats_attrs["name"]
  808. def RenderCatRast(self, cat_id):
  809. train_mgr, preview_mgr = self.iclass_frame.GetMapManagers()
  810. if not cat_id in self.added_cats_rasts:
  811. cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
  812. cat_name = self.cats_mgr.GetCategoryAttrs(cat_id)['name']
  813. self.UpdateCategoryRaster(cat_id, ['color'], render=False)
  814. train_mgr.AddLayer(cat_rast, alias=cat_name)
  815. self.added_cats_rasts.append(cat_id)
  816. else: # TODO settings
  817. train_mgr.Render()
  818. def SetData(self):
  819. self.iclass_frame.AddBands()
  820. self.added_cats_rasts = []
  821. def EmptyCategories(self):
  822. self.iclass_frame.OnCategoryManager(None)
  823. def SyncCats(self, cats_ids=None):
  824. self.cats_mgr.addedCategory.disconnect(self.AddStatistics)
  825. cats = self.stats_data.GetCategories()
  826. for c in cats:
  827. if cats_ids and c not in cats_ids:
  828. continue
  829. stats = self.stats_data.GetStatistics(c)
  830. self.cats_mgr.AddCategory(c, stats.name, stats.color, stats.nstd)
  831. self.cats_mgr.addedCategory.connect(self.AddStatistics)
  832. def CategoryChanged(self, cat):
  833. self.cats_mgr.SetSelectedCat(cat)
  834. def AddCategory(self, cat, name, color):
  835. self.cats_mgr.addedCategory.disconnect(self.AddStatistics)
  836. stats = self.stats_data.GetStatistics(cat)
  837. self.cats_mgr.AddCategory(
  838. cat_id=cat,
  839. name=name,
  840. color=color,
  841. nstd=stats.nstd)
  842. self.cats_mgr.addedCategory.connect(self.AddStatistics)
  843. def DeleteCategory(self, cat):
  844. self.cats_mgr.deletedCategory.disconnect(self.DeleteStatistics)
  845. self.cats_mgr.DeleteCategory(cat)
  846. self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
  847. def DeletAllCategories(self):
  848. self.cats_mgr.deletedCategory.disconnect(self.DeleteStatistics)
  849. cats = self.stats_data.GetCategories()
  850. for c in cats:
  851. self.cats_mgr.DeleteCategory(c)
  852. self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
  853. def SetCategory(self, cat, stats):
  854. self.cats_mgr.setCategoryAttrs.disconnect(self.SetStatistics)
  855. cats_attr = {}
  856. for attr in ['name', 'color', 'nstd']:
  857. if attr in stats:
  858. cats_attr[attr] = stats[attr]
  859. if cats_attr:
  860. self.cats_mgr.SetCategoryAttrs(cat, cats_attr)
  861. self.cats_mgr.setCategoryAttrs.connect(self.SetStatistics)
  862. def SetStatistics(self, cat_id, attrs_dict):
  863. self.stats_data.statisticsSet.disconnect(self.SetCategory)
  864. self.stats_data.GetStatistics(cat_id).SetStatistics(attrs_dict)
  865. self.stats_data.statisticsSet.connect(self.SetCategory)
  866. def AddStatistics(self, cat_id, name, color):
  867. self.stats_data.statisticsAdded.disconnect(self.AddCategory)
  868. self.stats_data.AddStatistics(cat_id, name, color)
  869. self.stats_data.statisticsAdded.connect(self.AddCategory)
  870. def DeleteStatistics(self, cat_id):
  871. self.stats_data.statisticsDeleted.disconnect(self.DeleteCategory)
  872. self.stats_data.DeleteStatistics(cat_id)
  873. self.stats_data.statisticsDeleted.connect(self.DeleteCategory)
  874. def GroupSet(self, group, subgroup):
  875. kwargs = {}
  876. if subgroup:
  877. kwargs['subgroup'] = subgroup
  878. res = RunCommand('i.group',
  879. flags='g',
  880. group=group,
  881. read=True, **kwargs).strip()
  882. if res.splitlines()[0]:
  883. bands = res.splitlines()
  884. self.scatt_mgr.SetBands(bands)