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