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