1"""
2@package vnet.vnet_core
3
4@brief Vector network analysis logic.
5
6Classes:
7 - vnet_core::VNETManager
8 - vnet_core::VNETAnalyses
9 - vnet_core::VNETHistory
10 - vnet_core::SnappingNodes
11
12(C) 2013 by the GRASS Development Team
13
14This program is free software under the GNU General Public License
15(>=v2). Read the file COPYING that comes with GRASS for details.
16
17@author Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa)
18@author Lukas Bocan <silent_bob centrum.cz> (turn costs support)
19@author Eliska Kyzlikova <eliska.kyzlikova gmail.com> (turn costs support)
20"""
21
22import os
23import six
24from grass.script.utils import try_remove
25from grass.script import core as grass
26from grass.script.task import cmdlist_to_tuple
27
28import wx
29
30from core import utils
31from core.gcmd import RunCommand, GMessage
32from core.gconsole import CmdThread, EVT_CMD_DONE, GConsole
33
34from gui_core.gselect import VectorDBInfo
35
36from vnet.vnet_data import VNETData, VNETTmpVectMaps, VectMap, History
37from vnet.vnet_utils import ParseMapStr, haveCtypes, GetNearestNodeCat
38
39from grass.pydispatch.signal import Signal
40
41
42class VNETManager:
43
44    def __init__(self, guiparent, giface):
45
46        self.data = {}
47
48        self.guiparent = guiparent
49        self.giface = giface
50        self.mapWin = giface.GetMapWindow()
51
52        self.goutput = GConsole(guiparent=guiparent, giface=self.giface)
53
54        self.vnet_data = VNETData(guiparent=guiparent, mapWin=self.mapWin)
55
56        self.results = {"analysis": None,
57                        "vect_map": None}  # TODO more results
58
59        # this class instance manages all temporary vector maps created during
60        # life of VNETDialog
61        self.tmp_maps = VNETTmpVectMaps(parent=guiparent, mapWin=self.mapWin)
62
63        # initialization of History class used for saving and reading data from file
64        # it is used for browsing analysis results
65        self.history = VNETHistory(
66            self.guiparent, self.vnet_data, self.tmp_maps)
67        self.analyses = VNETAnalyses(
68            self.vnet_data,
69            self.RunAnDone,
70            self.goutput,
71            self.tmp_maps)
72
73        self.snap_nodes = SnappingNodes(
74            self.giface, self.vnet_data, self.tmp_maps, self.mapWin)
75
76        self.ttbCreated = Signal('VNETManager.ttbCreated')
77        self.analysisDone = Signal('VNETManager.analysisDone')
78        self.pointsChanged = self.vnet_data.pointsChanged
79        self.parametersChanged = self.vnet_data.parametersChanged
80
81        self.snapping = self.snap_nodes.snapping
82        self.pointsChanged.connect(self.PointsChanged)
83
84    def __del__(self):
85        self.CleanUp()
86
87    def CleanUp(self):
88        """Removes temp layers, unregisters handlers and graphics"""
89
90        update = self.tmp_maps.DeleteAllTmpMaps()
91
92        self.vnet_data.CleanUp()
93
94        if update:
95            self.giface.updateMap.emit(render=True, renderVector=True)
96        else:
97            self.giface.updateMap.emit(render=False, renderVector=False)
98
99    def GetPointsManager(self):
100        return self.vnet_data.GetPointsData()
101
102    def GetGlobalTurnsData(self):
103        return self.vnet_data.GetGlobalTurnsData()
104
105    def RunAnalysis(self):
106
107        analysis, valid = self.vnet_data.GetParam("analysis")
108
109        params, err_params, flags = self.vnet_data.GetParams()
110        relevant_params = self.vnet_data.GetRelevantParams(analysis)
111
112        if not relevant_params:
113            return -1
114
115        if not self.vnet_data.InputsErrorMsgs(
116                _("Unable to perform analysis."),
117                analysis, params, flags, err_params, relevant_params):
118            return -2
119
120        if self.results["vect_map"]:
121            self.results["vect_map"].DeleteRenderLayer()
122
123        # history - delete data in buffer for hist step
124        self.history.DeleteNewHistStepData()
125
126        # create new map (included to history) for result of analysis
127        self.results["vect_map"] = self.history.NewTmpVectMapToHist(
128            'vnet_tmp_result')
129
130        if not self.results["vect_map"]:
131            return False
132
133        # for case there is some map with same name
134        # (when analysis does not produce any map, this map would have been shown as result)
135        RunCommand('g.remove', flags='f', type='vector',
136                   name=self.results["vect_map"].GetVectMapName())
137
138        # save data from
139        self.history._saveAnInputToHist(analysis, params, flags)
140
141        ret = self.analyses.RunAnalysis(
142            self.results["vect_map"].GetVectMapName(), params, flags)
143        if not ret:
144            return -3
145        else:
146            return 1
147
148    def RunAnDone(self, cmd, returncode, results):
149
150        self.results["analysis"] = cmd[0]
151
152        self.results["vect_map"].SaveVectMapState()
153
154        cmd, cmd_colors = self.vnet_data.GetLayerStyle()
155        self.results["vect_map"].AddRenderLayer(cmd, cmd_colors)
156
157        self.history.SaveHistStep()
158
159        self.analysisDone.emit()
160
161    def ShowResult(self, show):
162        # TODO can be more results e. g. smallest cut
163
164        if show:
165            self._checkResultMapChanged(self.results["vect_map"])
166            cmd, cmd_colors = self.vnet_data.GetLayerStyle()
167            self.results["vect_map"].AddRenderLayer(cmd, cmd_colors)
168        else:
169            self.results["vect_map"].DeleteRenderLayer()
170
171        self.giface.updateMap.emit(render=True, renderVector=True)
172
173    def GetAnalysisProperties(self, analysis=None):
174        return self.vnet_data.GetAnalysisProperties(analysis=analysis)
175
176    def GetResults(self):
177        return self.results["vect_map"]
178
179    def Undo(self):
180        self._updateDataForHistStep(self.history.Undo())
181        # SetUpdateMap TODO
182        return self.history.GetHistStep()
183
184    def Redo(self):
185        self._updateDataForHistStep(self.history.Redo())
186        # SetUpdateMap
187        return self.history.GetHistStep()
188
189    def _updateDataForHistStep(self, data):
190        if not data:
191            return
192
193        analysis, resultMapName, params, flags = data
194
195        self.results["analysis"] = analysis
196        self.vnet_data.SetParams(params, flags)
197
198        self.results["vect_map"].DeleteRenderLayer()
199        self.results["vect_map"] = self.tmp_maps.GetTmpVectMap(resultMapName)
200        self._checkResultMapChanged(self.results["vect_map"])
201
202        cmd, cmd_colors = self.vnet_data.GetLayerStyle()
203        self.results["vect_map"].AddRenderLayer(cmd, cmd_colors)
204
205        self.giface.updateMap.emit(render=True, renderVector=True)
206
207    def GetHistStep(self):
208        return self.history.GetHistStep()
209
210    def SetParams(self, params, flags):
211        self.vnet_data.SetParams(params, flags)
212
213    def GetParams(self):
214        params, inv_params, flags = self.vnet_data.GetParams()
215        return params, inv_params, flags
216
217    def GetParam(self, param):
218        return self.vnet_data.GetParam(param)
219
220    def _checkResultMapChanged(self, resultVectMap):
221        """Check if map was modified outside"""
222        if resultVectMap.VectMapState() == 0:
223            dlg = wx.MessageDialog(
224                parent=self,
225                message=_(
226                    "Temporary map '%s' with result " +
227                    "was changed outside vector network analysis tool.\n" +
228                    "Showed result may not correspond " +
229                    "original analysis result.") %
230                resultVectMap.GetVectMapName(),
231                caption=_("Result changed outside"),
232                style=wx.ICON_INFORMATION | wx.CENTRE)
233            dlg.ShowModal()
234            dlg.Destroy()
235
236    def IsSnappingActive(self):
237        return self.vnet_data.GetSnapping()
238
239    def Snapping(self, activate):
240        self.snap_nodes.ComputeNodes(activate)
241
242    def GetAnalyses(self):
243        return self.vnet_data.GetAnalyses()
244
245    def SettingsUpdated(self):
246        self.vnet_data.GetPointsData().SetPointDrawSettings()
247        if not self.results["vect_map"] or not self.tmp_maps.HasTmpVectMap(
248                self.results["vect_map"].GetVectMapName()):
249            self.giface.updateMap.emit(render=False, renderVector=False)
250        elif self.results["vect_map"].GetRenderLayer():
251            cmd, cmd_colors = self.vnet_data.GetLayerStyle()
252            self.results["vect_map"].AddRenderLayer(cmd, cmd_colors)
253
254            self.giface.updateMap.emit(render=True, renderVector=True)
255            # TODO optimization
256        else:
257            self.giface.updateMap.emit(render=False, renderVector=False)
258
259    def PointsChanged(self, method, kwargs):
260        self.giface.updateMap.emit(render=False, renderVector=False)
261
262    def CreateTttb(self, params):
263
264        outputMap = params["output"]
265        mapName, mapSet = ParseMapStr(outputMap)
266        if mapSet != grass.gisenv()['MAPSET']:
267            GMessage(parent=self,
268                     message=_("Map can be created only in current mapset"))
269            return False
270        existsMap = grass.find_file(name=mapName,
271                                    element='vector',
272                                    mapset=grass.gisenv()['MAPSET'])
273        if existsMap["name"]:
274            dlg = wx.MessageDialog(parent=self.guiparent,
275                                   message=_("Vector map %s already exists. " +
276                                             "Do you want to overwrite it?") %
277                                   (existsMap["fullname"]),
278                                   caption=_("Overwrite vector map"),
279                                   style=wx.YES_NO | wx.NO_DEFAULT |
280                                   wx.ICON_QUESTION | wx.CENTRE)
281            ret = dlg.ShowModal()
282            dlg.Destroy()
283            if ret == wx.ID_NO:
284                return False
285
286            cmdTtb = [
287                "v.net.turntable",
288                "input=" + params["input"],
289                "output=" + params["output"],
290                "arc_layer=" + params["arc_layer"],
291                "turn_layer=" + params["turn_layer"],
292                "turn_cat_layer=" + params["turn_cat_layer"],
293                "--overwrite",
294            ]
295
296            self.goutput.RunCmd(command=cmdTtb, onDone=self._createTtbDone)
297
298        return True
299
300    def _createTtbDone(self, event):
301
302        if event.returncode != 0:
303            GMessage(parent=self.guiparent,
304                     message=_("Creation of turntable failed."))
305            return
306        else:
307            params = {}
308            for c in event.cmd:
309                spl_c = c.split("=")
310                if len(spl_c) != 2:
311                    continue
312
313                if spl_c[0] and spl_c != "input":
314                    params[spl_c[0]] = spl_c[1]
315                if spl_c[0] == "output":
316                    params["input"] = spl_c[1]
317
318            self.vnet_data.SetParams(params, {})
319
320        self.ttbCreated.emit(returncode=event.returncode)
321
322    def SaveTmpLayer(self, layer_name):
323        """Permanently saves temporary map of analysis result"""
324        msg = _("Vector map with analysis result does not exist.")
325
326        if not hasattr(self.results["vect_map"], "GetVectMapName"):
327            GMessage(parent=self.guiparent,
328                     message=msg)
329            return
330
331        mapToAdd = self.results["vect_map"].GetVectMapName()
332        mapToAddEx = grass.find_file(name=mapToAdd,
333                                     element='vector',
334                                     mapset=grass.gisenv()['MAPSET'])
335
336        if not mapToAddEx["name"]:
337            GMessage(parent=self.guiparent,
338                     message=msg)
339            return
340
341        addedMap = layer_name
342        mapName, mapSet = ParseMapStr(addedMap)
343        if mapSet != grass.gisenv()['MAPSET']:
344            GMessage(parent=self.guiparent, message=_(
345                "Map can be saved only to currently set mapset"))
346            return
347        existsMap = grass.find_file(name=mapName,
348                                    element='vector',
349                                    mapset=grass.gisenv()['MAPSET'])
350        if existsMap["name"]:
351            dlg = wx.MessageDialog(parent=self.guiparent,
352                                   message=_("Vector map %s already exists. " +
353                                             "Do you want to overwrite it?") %
354                                   (existsMap["fullname"]),
355                                   caption=_("Overwrite vector map"),
356                                   style=wx.YES_NO | wx.NO_DEFAULT |
357                                   wx.ICON_QUESTION | wx.CENTRE)
358            ret = dlg.ShowModal()
359            if ret == wx.ID_NO:
360                dlg.Destroy()
361                return
362            dlg.Destroy()
363
364        RunCommand("g.copy",
365                   overwrite=True,
366                   vector=[self.results["vect_map"].GetVectMapName(), mapName])
367
368        if len(self.giface.GetLayerList().GetLayersByName(mapName)) == 0:
369            # TODO: get rid of insert
370            cmd, cmd_colors = self.vnet_data.GetLayerStyle()
371            cmd.insert(0, 'd.vect')
372            cmd.append('map=%s' % mapName)
373
374            self.giface.GetLayerList().AddLayer(ltype="vector",
375                                                name=mapName,
376                                                cmd=cmd,
377                                                checked=True)
378            if cmd_colors:
379                layerStyleVnetColors = cmdlist_to_tuple(cmd_colors)
380
381                RunCommand(layerStyleVnetColors[0],
382                           **layerStyleVnetColors[1])
383        else:
384            self.giface.updateMap.emit(render=True, renderVector=True)
385
386
387class VNETAnalyses:
388
389    def __init__(self, data, onAnDone, goutput, tmp_maps):
390        self.data = data
391
392        self.pts_data = self.data.GetPointsData()
393        self.tmp_maps = tmp_maps
394
395        self.onAnDone = onAnDone
396        self.goutput = goutput
397
398    def RunAnalysis(self, output, params, flags):
399        """Perform network analysis"""
400
401        analysis, valid = self.data.GetParam("analysis")
402
403        catPts = self._getPtByCat(analysis)
404
405        if analysis == "v.net.path":
406            self._vnetPathRunAn(analysis, output, params, flags, catPts)
407        elif flags["t"]:
408            self._runTurnsAn(analysis, output, params, flags, catPts)
409        else:
410            self._runAn(analysis, output, params, flags, catPts)
411
412    def _vnetPathRunAn(self, analysis, output, params, flags, catPts):
413        """Called when analysis is run for v.net.path module"""
414        if self.pts_data.GetPointsCount() < 1:
415            return False
416        cats = self.data.GetAnalysisProperties()["cmdParams"]["cats"]
417
418        # Creates part of cmd fro analysis
419        cmdParams = [analysis]
420        cmdParams.extend(self._setInputParams(analysis, params, flags))
421        cmdParams.append("output=" + output)
422
423        cmdPts = []
424        for cat in cats:
425            if len(catPts[cat[0]]) < 1:  # TODO
426                GMessage(
427                    message=_("Please choose '%s' and '%s' point.") %
428                    (cats[0][1], cats[1][1]))
429                return False
430            cmdPts.append(catPts[cat[0]][0])
431
432        resId = 1
433        inpPoints = str(resId) + " " + str(cmdPts[0][0]) + " " + str(
434            cmdPts[0][1]) + " " + str(cmdPts[1][0]) + " " + str(cmdPts[1][1])
435
436        self.coordsTmpFile = grass.tempfile()
437        coordsTmpFileOpened = open(self.coordsTmpFile, 'w')
438        coordsTmpFileOpened.write(inpPoints)
439        coordsTmpFileOpened.close()
440
441        if flags["t"]:
442            cmdParams.append("-t")
443
444            self.tmpTurnAn = AddTmpMapAnalysisMsg(
445                "vnet_tunr_an_tmp", self.tmp_maps)
446            if not self.tmpTurnAn:
447                return False
448
449            mapName, mapSet = ParseMapStr(self.tmpTurnAn.GetVectMapName())
450            cmdCopy = [
451                "g.copy",
452                "vector=%s,%s" % (params["input"], mapName),
453                "--overwrite",
454            ]
455            cmdParams.append("input=" + self.tmpTurnAn.GetVectMapName())
456
457            ret, msg, err = RunCommand(
458                'g.copy', getErrorMsg=True, vector="%s,%s" %
459                (params['input'], mapName), read=True, overwrite=True)
460
461            self._updateTtbByGlobalCosts(self.tmpTurnAn.GetVectMapName(),
462                                         int(params["turn_layer"]))
463
464            # self._prepareCmd(cmdCopy)
465            #self.goutput.RunCmd(command = cmdCopy)
466        else:
467            cmdParams.append("input=" + params["input"])
468
469        cmdParams.append("file=" + self.coordsTmpFile)
470
471        cmdParams.append("dmax=" + str(params["max_dist"]))
472
473        cmdParams.append("--overwrite")
474        self._prepareCmd(cmd=cmdParams)
475
476        if flags["t"]:
477            self.goutput.RunCmd(
478                command=cmdParams,
479                onDone=self._vnetPathRunTurnsAnDone)
480        else:
481            self.goutput.RunCmd(
482                command=cmdParams,
483                onDone=self._vnetPathRunAnDone)
484
485    def _vnetPathRunTurnsAnDone(self, event):
486        # TODO
487        # self.tmp_maps.DeleteTmpMap(self.tmpTurnAn)
488        self._vnetPathRunAnDone(event)
489
490    def _vnetPathRunAnDone(self, event):
491        """Called when v.net.path analysis is done"""
492        try_remove(self.coordsTmpFile)
493
494        self._onDone(event)
495
496    def _onDone(self, event):
497        for c in event.cmd:
498            if "output=" in c:
499                output = c.split("=")[1]
500                break
501
502        self.onAnDone(event.cmd, event.returncode, output)
503
504    def _runTurnsAn(self, analysis, output, params, flags, catPts):
505
506        # Creates part of cmd fro analysis
507        cmdParams = [analysis]
508        cmdParams.extend(self._setInputParams(analysis, params, flags))
509        cmdParams.append("output=" + output)
510
511        cats = {}
512        for cat_name, pts_coor in six.iteritems(catPts):
513
514            for coor in pts_coor:
515                cat_num = str(
516                    GetNearestNodeCat(
517                        e=coor[0],
518                        n=coor[1],
519                        field=int(
520                            params["turn_cat_layer"],
521                            tresh=params["max_dist"]),
522                        vectMap=params["input"]))
523                if cat_num < 0:
524                    continue
525                if cat_name in cats:
526                    cats[cat_name].append(cat_num)
527                else:
528                    cats[cat_name] = [cat_num]
529
530        for cat_name, cat_nums in six.iteritems(cats):
531            cmdParams.append(cat_name + "=" + ",".join(cat_nums))
532
533        self.tmpTurnAn = AddTmpMapAnalysisMsg(
534            "vnet_tunr_an_tmp", self.tmp_maps)
535        if not self.tmpTurnAn:
536            return False
537
538        # create and run commands which goes to analysis thread
539
540        mapName, mapSet = ParseMapStr(self.tmpTurnAn.GetVectMapName())
541        cmdCopy = [
542            "g.copy",
543            "vector=%s,%s" % (params['input'], mapName),
544            "--overwrite",
545        ]
546        cmdParams.append("input=" + self.tmpTurnAn.GetVectMapName())
547
548        ret, msg, err = RunCommand('g.copy',
549                                   getErrorMsg=True,
550                                   vector="%s,%s" % (params['input'], mapName),
551                                   read=True,
552                                   overwrite=True)
553
554        self._updateTtbByGlobalCosts(self.tmpTurnAn.GetVectMapName(),
555                                     int(params["turn_layer"]))
556
557        self._setCmdForSpecificAn(cmdParams)
558
559        cmdParams.append("-t")
560
561        self._prepareCmd(cmdParams)
562        self.goutput.RunCmd(command=cmdParams, onDone=self._runTurnsAnDone)
563
564    def _updateTtbByGlobalCosts(self, vectMapName, tlayer):
565        # TODO get layer number do not use it directly
566        intervals = self.turnsData["global"].GetData()
567
568        cmdUpdGlob = [
569            "v.db.update",
570            "map=", self.inputData["input"].GetValue(),
571            "layer=%d" % tlayer,
572            "column=cost",
573        ]
574
575        dbInfo = VectorDBInfo(vectMapName)
576        table = dbInfo.GetTable(tlayer)
577        driver, database = dbInfo.GetDbSettings(tlayer)
578
579        sqlFile = grass.tempfile()
580        sqlFile_f = open(sqlFile, 'w')
581
582        for ival in intervals:
583            from_angle = ival[0]
584            to_angle = ival[1]
585            cost = ival[2]
586
587            if to_angle < from_angle:
588                to_angle = math.pi * 2 + to_angle
589            # if angle < from_angle:
590            #    angle = math.pi * 2  + angle
591
592            where = " WHERE (((angle < {0}) AND ({2} + angle >= {0} AND {2} + angle < {1})) OR \
593                            ((angle >= {0}) AND (angle >= {0} AND angle < {1}))) AND cost==0.0 ".format(str(from_angle), str(to_angle), str(math.pi * 2))
594
595            stm = ("UPDATE %s SET cost=%f " % (table, cost)) + where + ";\n"
596            sqlFile_f.write(stm)
597
598        sqlFile_f.close()
599
600        # TODO imporve parser and run in thread
601
602        ret, msg, err = RunCommand('db.execute',
603                                   getErrorMsg=True,
604                                   input=sqlFile,
605                                   read=True,
606                                   driver=driver,
607                                   database=database)
608
609        try_remove(sqlFile)
610
611    def _runTurnsAnDone(self, event):
612        """Called when analysis is done"""
613        # self.tmp_maps.DeleteTmpMap(self.tmpTurnAn) #TODO remove earlier
614        # (OnDone lambda?)
615
616        self._onDone(event)
617
618    def _runAn(self, analysis, output, params, flags, catPts):
619        """Called for all v.net.* analysis (except v.net.path)"""
620
621        # Creates part of cmd fro analysis
622        cmdParams = [analysis]
623        cmdParams.extend(self._setInputParams(analysis, params, flags))
624        cmdParams.append("output=" + output)
625
626        cats = self.data.GetAnalysisProperties()["cmdParams"]["cats"]
627
628        if len(cats) > 1:
629            for cat in cats:
630                if len(catPts[cat[0]]) < 1:
631                    GMessage(parent=self,
632                             message=_("Please choose '%s' and '%s' point.")
633                             % (cats[0][1], cats[1][1]))
634                    return False
635        else:
636            for cat in cats:
637                if len(catPts[cat[0]]) < 2:
638                    GMessage(parent=self,
639                             message=_("Please choose at least two points."))
640                    return False
641
642        # TODO add also to thread for analysis?
643        vcatResult = RunCommand("v.category",
644                                input=params['input'],
645                                option="report",
646                                flags="g",
647                                read=True)
648
649        vcatResult = vcatResult.splitlines()
650        for cat in vcatResult:  # TODO
651            cat = cat.split()
652            if "all" in cat:
653                maxCat = int(cat[4])
654                break
655
656        layerNum = params["node_layer"]
657
658        pt_ascii, catsNums = self._getAsciiPts(catPts=catPts,
659                                               maxCat=maxCat,
660                                               layerNum=layerNum)
661
662        # TODO better tmp files cleanup (make class for managing tmp files)
663        self.tmpPtsAsciiFile = grass.tempfile()
664        tmpPtsAsciiFileOpened = open(self.tmpPtsAsciiFile, 'w')
665        tmpPtsAsciiFileOpened.write(pt_ascii)
666        tmpPtsAsciiFileOpened.close()
667
668        self.tmpInPts = AddTmpMapAnalysisMsg("vnet_tmp_in_pts", self.tmp_maps)
669        if not self.tmpInPts:
670            return False
671
672        self.tmpInPtsConnected = AddTmpMapAnalysisMsg(
673            "vnet_tmp_in_pts_connected", self.tmp_maps)
674        if not self.tmpInPtsConnected:
675            return False
676
677        cmdParams.append("input=" + self.tmpInPtsConnected.GetVectMapName())
678        cmdParams.append("--overwrite")
679
680        self._setCmdForSpecificAn(cmdParams)
681
682        for catName, catNum in six.iteritems(catsNums):
683            if catNum[0] == catNum[1]:
684                cmdParams.append(catName + "=" + str(catNum[0]))
685            else:
686                cmdParams.append(
687                    catName + "=" + str(catNum[0]) + "-" + str(catNum[1]))
688
689        # create and run commands which goes to analysis thread
690        cmdVEdit = [
691            "v.edit",
692            "map=" + self.tmpInPts.GetVectMapName(),
693            "input=" + self.tmpPtsAsciiFile,
694            "tool=create",
695            "--overwrite",
696            "-n"
697        ]
698
699        self._prepareCmd(cmdVEdit)
700        self.goutput.RunCmd(command=cmdVEdit)
701
702        cmdVNet = [
703            "v.net",
704            "points=" + self.tmpInPts.GetVectMapName(),
705            "input=" + params['input'],
706            "output=" + self.tmpInPtsConnected.GetVectMapName(),
707            "arc_layer=" + params["arc_layer"],
708            "node_layer=" + params["node_layer"],
709            "operation=connect",
710            "thresh=" + str(params["max_dist"]),
711            "--overwrite"
712        ]  # TODO snapping to nodes optimization
713
714        self._prepareCmd(cmdVNet)
715        self.goutput.RunCmd(command=cmdVNet)
716
717        self._prepareCmd(cmdParams)
718        self.goutput.RunCmd(command=cmdParams, onDone=self._runAnDone)
719
720    def _runAnDone(self, event):
721        """Called when analysis is done"""
722        self.tmp_maps.DeleteTmpMap(
723            self.tmpInPts)  # TODO remove earlier (OnDone lambda?)
724        self.tmp_maps.DeleteTmpMap(self.tmpInPtsConnected)
725        try_remove(self.tmpPtsAsciiFile)
726
727        if event.cmd[0] == "v.net.flow":
728            self.tmp_maps.DeleteTmpMap(self.vnetFlowTmpCut)
729
730        self._onDone(event)
731
732    def _setInputParams(self, analysis, params, flags):
733        """Return list of chosen values (vector map, layers).
734
735        The list items are in form to be used in command for analysis e.g. 'arc_layer=1'.
736        """
737
738        inParams = []
739        for col, v in six.iteritems(self.data.GetAnalysisProperties()["cmdParams"]
740                                                                     ["cols"]):
741
742            if "inputField" in v:
743                colInptF = v["inputField"]
744            else:
745                colInptF = col
746
747            inParams.append(col + '=' + params[colInptF])
748
749        for layer in ['arc_layer', 'node_layer',
750                      'turn_layer', 'turn_cat_layer']:
751            if not flags["t"] and layer in ['turn_layer', 'turn_cat_layer']:
752                continue
753            # TODO
754            if flags["t"] and layer == 'node_layer':
755                inParams.append(layer + "=" + params['turn_cat_layer'])
756                continue
757
758            inParams.append(layer + "=" + params[layer])
759
760        return inParams
761
762    def _getPtByCat(self, analysis):
763        """Return points separated by theirs categories"""
764        anProps = self.data.GetAnalysisProperties()
765        cats = anProps["cmdParams"]["cats"]
766
767        ptByCats = {}
768        for cat in anProps["cmdParams"]["cats"]:
769            ptByCats[cat[0]] = []
770
771        for i in range(self.pts_data.GetPointsCount()):
772            pt_data = self.pts_data.GetPointData(i)
773            if pt_data["use"]:
774                for i_cat, cat in enumerate(cats):
775                    # i_cat + 1 - we ave to increment it because
776                    # pt_data["type"] includes "" in first place
777                    if (i_cat + 1) == pt_data["type"] or len(ptByCats) == 1:
778                        coords = (pt_data['e'], pt_data['n'])
779                        ptByCats[cat[0]].append(coords)
780
781        return ptByCats
782
783    def _getAsciiPts(self, catPts, maxCat, layerNum):
784        """Return points separated by categories in GRASS ASCII vector representation"""
785        catsNums = {}
786        pt_ascii = ""
787        catNum = maxCat
788
789        for catName, pts in six.iteritems(catPts):
790
791            catsNums[catName] = [catNum + 1]
792            for pt in pts:
793                catNum += 1
794                pt_ascii += "P 1 1\n"
795                pt_ascii += str(pt[0]) + " " + str(pt[1]) + "\n"
796                pt_ascii += str(layerNum) + " " + str(catNum) + "\n"
797
798            catsNums[catName].append(catNum)
799
800        return pt_ascii, catsNums
801
802    def _prepareCmd(self, cmd):
803        """Helper function for preparation of cmd list into form for RunCmd method"""
804        for c in cmd[:]:
805            if c.find("=") == -1:
806                continue
807            v = c.split("=")
808            if len(v) != 2:
809                cmd.remove(c)
810            elif not v[1].strip():
811                cmd.remove(c)
812
813    def _setCmdForSpecificAn(self, cmdParams):
814        # append parameters needed for particular analysis
815        if cmdParams[0] == "v.net.distance":
816            cmdParams.append("from_layer=1")
817            cmdParams.append("to_layer=1")
818        elif cmdParams[0] == "v.net.flow":
819            # self.vnetFlowTmpCut =
820            # self.NewTmpVectMapToHist('vnet_tmp_flow_cut') TODO
821            self.vnetFlowTmpCut = AddTmpMapAnalysisMsg(
822                'vnet_tmp_flow_cut', self.tmp_maps)
823            if not self.vnetFlowTmpCut:
824                return
825            cmdParams.append("cut=" + self.vnetFlowTmpCut.GetVectMapName())
826        elif cmdParams[0] == "v.net.iso":
827            costs, valid = self.data.GetParam("iso_lines")  # TODO valid
828            cmdParams.append("costs=" + costs)
829
830
831class VNETHistory():
832
833    def __init__(self, guiparent, data, tmp_maps):
834        self.history = History()
835        self.guiparent = guiparent
836
837        self.tmp_maps = tmp_maps
838
839        # variable, which appends  unique number to every name of map, which is
840        # saved into history
841        self.histTmpVectMapNum = 0
842
843        self.data = data
844
845    def Undo(self):
846        """Step back in history"""
847        histStepData = self.history.GetPrev()
848
849        if histStepData:
850            return self._updateHistStepData(histStepData)
851
852        return None
853
854    def Redo(self):
855        """Step forward in history"""
856        histStepData = self.history.GetNext()
857
858        if histStepData:
859            return self._updateHistStepData(histStepData)
860
861        return None
862
863    def GetHistStep(self):
864        return self.history.GetCurrHistStep(), self.history.GetStepsNum()
865
866    def SaveHistStep(self):
867        """Save new step into history"""
868        removedHistData = self.history.SaveHistStep()
869
870        if not removedHistData:
871            return
872
873        # delete temporary maps in history steps which were deleted
874        for removedStep in six.itervalues(removedHistData):
875            mapsNames = removedStep["tmp_data"]["maps"]
876            for vectMapName in mapsNames:
877                tmpMap = self.tmp_maps.GetTmpVectMap(vectMapName)
878                self.tmp_maps.DeleteTmpMap(tmpMap)
879
880    def DeleteNewHistStepData(self):
881        # history - delete data in buffer for hist step
882        self.history.DeleteNewHistStepData()
883        # empty list for maps to be saved to history
884        self.tmpVectMapsToHist = []
885
886    def _updateHistStepData(self, histStepData):
887        """Updates dialog according to chosen history step"""
888        # set analysis module
889        analysis = histStepData["vnet_modules"]["curr_module"]
890        self.data.SetParams({"analysis": analysis}, {})
891
892        pts = []
893        # add points to list
894        for iPt in range(len(histStepData["points"])):
895            ptDataHist = histStepData["points"]["pt" + str(iPt)]
896
897            e, n = ptDataHist["coords"]
898            pt_data = {"e": e, "n": n}
899
900            pt_data['type'] = int(ptDataHist["catIdx"])
901
902            pt_data['topology'] = ptDataHist["topology"]
903
904            pt_data['use'] = ptDataHist["checked"]
905
906            pts.append(pt_data)
907
908        self.data.GetPointsData().SetPoints(pts)
909
910        # update analysis result maps
911        mapsNames = histStepData["tmp_data"]["maps"]
912        for m in mapsNames:
913            if "vnet_tmp_result" in m:
914                resultMapName = m
915                break
916
917        # update parameters
918        params = {}
919        histInputData = histStepData["an_params"]
920        for inpName, inp in six.iteritems(histInputData):
921            params[inpName] = str(inp)
922            if inpName == "input":
923                inpMap = inp
924
925        prevInpModTime = str(histStepData["other"]["input_modified"])
926        currInpModTime = VectMap(None, inpMap).GetLastModified()
927
928        if currInpModTime.strip() != prevInpModTime.strip():
929            dlg = wx.MessageDialog(
930                parent=self.guiparent,
931                message=_(
932                    "Input map '%s' for analysis was changed outside " +
933                    "vector network analysis tool.\n" +
934                    "Topology column may not " +
935                    "correspond to changed situation.") %
936                inpMap,
937                caption=_("Input changed outside"),
938                style=wx.ICON_INFORMATION | wx.CENTRE)
939            dlg.ShowModal()
940            dlg.Destroy()
941
942        # TODO
943        flags = {}
944        return analysis, resultMapName, params, flags
945
946    def _saveAnInputToHist(self, analysis, params, flags):
947        """Save all data needed for analysis into history buffer"""
948        pts_num = self.data.GetPointsData().GetPointsCount()
949
950        for pt_id in range(pts_num):
951            data = self.data.GetPointsData().GetPointData(pt_id)
952
953            ptName = "pt" + str(pt_id)
954
955            coords = [data["e"], data["n"]]
956            self.history.Add(key="points",
957                             subkey=[ptName, "coords"],
958                             value=coords)
959
960            self.history.Add(key="points",
961                             subkey=[ptName, "catIdx"],
962                             value=data['type'])
963
964            self.history.Add(key="points",
965                             subkey=[ptName, "topology"],
966                             value=data['topology'])
967
968            self.history.Add(key="points",
969                             subkey=[ptName, "checked"],
970                             value=data["use"])
971
972            for param, value in six.iteritems(params):
973
974                if param == "input":
975                    inpMap = VectMap(self, value)
976                    self.history.Add(key="other",
977                                     subkey="input_modified",
978                                     value=inpMap.GetLastModified())
979                    param_val = value
980                else:
981                    param_val = value
982
983                self.history.Add(key="an_params",
984                                 subkey=param,
985                                 value=param_val)
986
987        self.history.Add(key="vnet_modules",
988                         subkey="curr_module",
989                         value=analysis)
990
991    def NewTmpVectMapToHist(self, prefMapName):
992        """Add new vector map, which will be saved into history step"""
993
994        mapName = prefMapName + str(self.histTmpVectMapNum)
995        self.histTmpVectMapNum += 1
996
997        tmpMap = AddTmpMapAnalysisMsg(mapName, self.tmp_maps)
998        if not tmpMap:
999            return tmpMap
1000
1001        self.tmpVectMapsToHist.append(tmpMap.GetVectMapName())
1002        self.history.Add(key="tmp_data",
1003                         subkey="maps",
1004                         value=self.tmpVectMapsToHist)
1005
1006        return tmpMap
1007
1008
1009def AddTmpMapAnalysisMsg(mapName, tmp_maps):  # TODO
1010    """Wraped AddTmpVectMap"""
1011    msg = _("Temporary map %s  already exists.\n" +
1012            "Do you want to continue in analysis and overwrite it?") \
1013        % (mapName + '@' + grass.gisenv()['MAPSET'])
1014    tmpMap = tmp_maps.AddTmpVectMap(mapName, msg)
1015    return tmpMap
1016
1017
1018class SnappingNodes(wx.EvtHandler):
1019
1020    def __init__(self, giface, data, tmp_maps, mapWin):
1021
1022        self.giface = giface
1023        self.data = data
1024        self.tmp_maps = tmp_maps
1025        self.mapWin = mapWin
1026
1027        wx.EvtHandler.__init__(self)
1028        self.snapping = Signal('VNETManager.snapping')
1029
1030        # Stores all data related to snapping
1031        self.snapData = {}
1032
1033    def ComputeNodes(self, activate):
1034        """Start/stop snapping mode"""
1035
1036        if not haveCtypes:
1037            GMessage(parent=self,
1038                     message=_("Unable to use ctypes. \n") +
1039                     _("Snapping mode can not be activated."))
1040            return -1
1041
1042        if not activate:
1043
1044            if self.tmp_maps.HasTmpVectMap("vnet_snap_points"):
1045                self.snapPts.DeleteRenderLayer()
1046
1047                self.giface.updateMap.emit(render=False, renderVector=False)
1048
1049            if 'cmdThread' in self.snapData:
1050                self.snapData['cmdThread'].abort()
1051
1052            self.data.SetSnapping(False)
1053
1054            self.snapping.emit(evt="deactivated")
1055
1056            return -1
1057
1058        self.data.SetSnapping(activate)
1059
1060        params, inv_params, flags = self.data.GetParams()
1061        if not self.data.InputsErrorMsgs(
1062                msg=_("Snapping mode can not be activated."),
1063                analysis=None, params=params, inv_params=inv_params, flags=flags,
1064                relevant_params=["input", "node_layer"]):
1065            return -1
1066
1067        if not self.tmp_maps.HasTmpVectMap("vnet_snap_points"):
1068            endStr = _(
1069                "Do you really want to activate snapping and overwrite it?")
1070            self.snapPts = self.tmp_maps.AddTmpVectMap(
1071                "vnet_snap_points", endStr)
1072
1073            if not self.snapPts:
1074                return -1
1075
1076        elif self.snapPts.VectMapState() == 0:
1077            dlg = wx.MessageDialog(
1078                message=_(
1079                    "Temporary map '%s' was changed outside " +
1080                    "vector analysis tool.\n"
1081                    "Do you really want to activate " +
1082                    "snapping and overwrite it? ") %
1083                self.snapPts.GetVectMapName(),
1084                caption=_("Overwrite map"),
1085                style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
1086
1087            ret = dlg.ShowModal()
1088            dlg.Destroy()
1089
1090            if ret == wx.ID_NO:
1091                self.tmp_maps.DeleteTmpMap(self.snapPts)
1092                return -1
1093
1094        self.data.SetSnapping(True)
1095
1096        inpFullName = params["input"]
1097        inpName, mapSet = inpFullName.split("@")
1098        computeNodes = True
1099
1100        if "inputMap" not in self.snapData:
1101            pass
1102        elif inpFullName != self.snapData["inputMap"].GetVectMapName():
1103            self.snapData["inputMap"] = VectMap(None, inpFullName)
1104        elif self.snapData["inputMap"].VectMapState() == 1:
1105            computeNodes = False
1106
1107        # new map needed
1108        if computeNodes:
1109            if 'cmdThread' not in self.snapData:
1110                self.snapData['cmdThread'] = CmdThread(self)
1111            else:
1112                self.snapData['cmdThread'].abort()
1113
1114            cmd = ["v.to.points", "input=" + params['input'],
1115                                  "output=" + self.snapPts.GetVectMapName(),
1116                                  "use=node", "--overwrite"]
1117            # process GRASS command with argument
1118            self.snapData["inputMap"] = VectMap(None, inpFullName)
1119            self.snapData["inputMap"].SaveVectMapState()
1120
1121            self.Bind(EVT_CMD_DONE, self._onNodesDone)
1122            self.snapData['cmdThread'].RunCmd(cmd)
1123
1124            self.snapping.emit(evt="computing_points")
1125
1126            return 0
1127        # map is already created and up to date for input data
1128        else:
1129            self.snapPts.AddRenderLayer()
1130
1131            self.giface.updateMap.emit(render=True, renderVector=True)
1132
1133            self.snapping.emit(evt="computing_points_done")
1134
1135            return 1
1136
1137    def _onNodesDone(self, event):
1138        """Update map window, when map with nodes to snap is created"""
1139        if not event.aborted:
1140            self.snapPts.SaveVectMapState()
1141            self.snapPts.AddRenderLayer()
1142
1143            self.giface.updateMap.emit(render=True, renderVector=True)
1144
1145            self.snapping.emit(evt="computing_points_done")
1146