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