controllers.py 41 KB

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