controllers.py 37 KB

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