1""" 2@package vnet.vnet_data 3 4@brief Vector network analysis classes for data managment. 5 6Classes: 7 - vnet_data::VNETData 8 - vnet_data::VNETPointsData 9 - vnet_data::VNETAnalysisParameters 10 - vnet_data::VNETAnalysesProperties 11 - vnet_data::VNETTmpVectMaps 12 - vnet_data::VectMap 13 - vnet_data::History 14 - vnet_data::VNETGlobalTurnsData 15 16(C) 2013-2014 by the GRASS Development Team 17 18This program is free software under the GNU General Public License 19(>=v2). Read the file COPYING that comes with GRASS for details. 20 21@author Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa) 22@author Lukas Bocan <silent_bob centrum.cz> (turn costs support) 23@author Eliska Kyzlikova <eliska.kyzlikova gmail.com> (turn costs support) 24""" 25import os 26import six 27from copy import deepcopy 28 29from grass.script.utils import try_remove 30from grass.script import core as grass 31from grass.script.task import cmdlist_to_tuple 32 33import wx 34 35 36from core import utils 37from core.gcmd import RunCommand, GMessage 38from core.settings import UserSettings 39 40from vnet.vnet_utils import ParseMapStr, SnapToNode 41 42from gui_core.gselect import VectorDBInfo 43from grass.pydispatch.signal import Signal 44 45from vnet.vnet_utils import DegreesToRadians, RadiansToDegrees 46 47 48class VNETData: 49 50 def __init__(self, guiparent, mapWin): 51 52 # setting initialization 53 self._initSettings() 54 55 self.guiparent = guiparent 56 57 self.an_props = VNETAnalysesProperties() 58 self.an_params = VNETAnalysisParameters(self.an_props) 59 60 self.an_points = VNETPointsData(mapWin, self.an_props, self.an_params) 61 62 self.global_turns = VNETGlobalTurnsData() 63 64 self.pointsChanged = self.an_points.pointsChanged 65 self.parametersChanged = self.an_params.parametersChanged 66 67 def CleanUp(self): 68 self.an_points.CleanUp() 69 70 def GetAnalyses(self): 71 return self.an_props.used_an 72 73 def GetPointsData(self): 74 return self.an_points 75 76 def GetGlobalTurnsData(self): 77 return self.global_turns 78 79 def GetRelevantParams(self, analysis=None): 80 if analysis: 81 return self.an_props.GetRelevantParams(analysis) 82 else: 83 analysis, valid = self.an_params.GetParam("analysis") 84 return self.an_props.GetRelevantParams(analysis) 85 86 def GetAnalysisProperties(self, analysis=None): 87 if analysis: 88 return self.an_props[analysis] 89 else: 90 analysis, valid = self.an_params.GetParam("analysis") 91 return self.an_props[analysis] 92 93 def GetParam(self, param): 94 return self.an_params.GetParam(param) 95 96 def GetParams(self): 97 return self.an_params.GetParams() 98 99 def SetParams(self, params, flags): 100 return self.an_params.SetParams(params, flags) 101 102 def SetSnapping(self, activate): 103 self.an_points.SetSnapping(activate) 104 105 def GetSnapping(self): 106 return self.an_points.GetSnapping() 107 108 def GetLayerStyle(self): 109 """Returns cmd for d.vect, with set style for analysis result""" 110 analysis, valid = self.an_params.GetParam("analysis") 111 112 resProps = self.an_props[analysis]["resultProps"] 113 114 width = UserSettings.Get( 115 group='vnet', 116 key='res_style', 117 subkey="line_width") 118 layerStyleCmd = ["layer=1", 'width=' + str(width)] 119 120 if "catColor" in resProps: 121 layerStyleCmd.append('flags=c') 122 elif "singleColor" in resProps: 123 col = UserSettings.Get( 124 group='vnet', 125 key='res_style', 126 subkey="line_color") 127 layerStyleCmd.append( 128 'color=' + str(col[0]) + ':' + str(col[1]) + ':' + str(col[2])) 129 130 layerStyleVnetColors = [] 131 if "attrColColor" in resProps: 132 colorStyle = UserSettings.Get( 133 group='vnet', key='res_style', subkey="color_table") 134 invert = UserSettings.Get( 135 group='vnet', 136 key='res_style', 137 subkey="invert_colors") 138 139 layerStyleVnetColors = [ 140 "v.colors", 141 "color=" + colorStyle, 142 "column=" + resProps["attrColColor"], 143 ] 144 if invert: 145 layerStyleVnetColors.append("-n") 146 147 return layerStyleCmd, layerStyleVnetColors 148 149 def InputsErrorMsgs(self, msg, analysis, params, flags, 150 inv_params, relevant_params): 151 """Checks input data in Parameters tab and shows messages if some value is not valid 152 153 :param str msg: message added to start of message string 154 :return: True if checked inputs are OK 155 :return: False if some of checked inputs is not ok 156 """ 157 158 if flags["t"] and "turn_layer" not in relevant_params: 159 GMessage( 160 parent=self.guiparent, message=_( 161 "Module <%s> does not support turns costs." % 162 analysis)) 163 return False 164 165 errMapStr = "" 166 if 'input' in inv_params: 167 if params['input']: 168 errMapStr = _("Vector map '%s' does not exist.") % ( 169 params['input']) 170 else: 171 errMapStr = _("Vector map was not chosen.") 172 173 if errMapStr: 174 GMessage(parent=self.guiparent, 175 message=msg + "\n" + errMapStr) 176 return False 177 178 errLayerStr = "" 179 vals = { 180 'arc_layer': _("arc layer"), 181 'node_layer': _("node layer"), 182 'turn_layer': _("turntable layer"), 183 'turn_cat_layer': _("unique categories layer") 184 } 185 for layer, layerLabel in six.iteritems(vals): 186 187 if layer in ["turn_layer", "turn_cat_layer"] and not flags["t"]: 188 continue 189 if layer in inv_params: 190 if params[layer]: 191 errLayerStr += _("Chosen %s '%s' does not exist in vector map '%s'.\n") % ( 192 layerLabel, params[layer], params['input']) 193 else: 194 errLayerStr += _("Choose existing %s.\n") % \ 195 (layerLabel) 196 if errLayerStr: 197 GMessage(parent=self.guiparent, 198 message=msg + "\n" + errLayerStr) 199 return False 200 201 errColStr = "" 202 for col in ["arc_column", "arc_backward_column", "node_column"]: 203 if params[col] and col in inv_params and col in relevant_params: 204 errColStr += _("Chosen column '%s' does not exist in attribute table of layer '%s' of vector map '%s'.\n") % ( 205 params[col], params[layer], params['input']) 206 207 if errColStr: 208 GMessage(parent=self.guiparent, 209 message=msg + "\n" + errColStr) 210 return False 211 212 return True 213 214 def _initSettings(self): 215 """Initialization of settings (if not already defined)""" 216 # initializes default settings 217 initSettings = [ 218 ['res_style', 'line_width', 5], 219 ['res_style', 'line_color', (192, 0, 0)], 220 ['res_style', 'color_table', 'byr'], 221 ['res_style', 'invert_colors', False], 222 ['point_symbol', 'point_size', 10], 223 ['point_symbol', 'point_width', 2], 224 ['point_colors', "unused", (131, 139, 139)], 225 ['point_colors', "used1cat", (192, 0, 0)], 226 ['point_colors', "used2cat", (0, 0, 255)], 227 ['point_colors', "selected", (9, 249, 17)], 228 ['other', "snap_tresh", 10], 229 ['other', "max_hist_steps", 5] 230 ] 231 232 for init in initSettings: 233 UserSettings.ReadSettingsFile() 234 UserSettings.Append(dict=UserSettings.userSettings, 235 group='vnet', 236 key=init[0], 237 subkey=init[1], 238 value=init[2], 239 overwrite=False) 240 241 242class VNETPointsData: 243 244 def __init__(self, mapWin, an_data, an_params): 245 246 self.mapWin = mapWin 247 self.an_data = an_data 248 self.an_params = an_params 249 250 # information, whether mouse event handler is registered in map window 251 self.handlerRegistered = False 252 253 self.pointsChanged = Signal('VNETPointsData.pointsChanged') 254 self.an_params.parametersChanged.connect(self.ParametersChanged) 255 256 self.snapping = False 257 258 self.data = [] 259 self.cols = {"name": ['use', 'type', 'topology', 'e', 'n'], 260 "label": [_('use'), _('type'), _('topology'), 'e', 'n'], 261 # TDO 262 "type": [None, ["", _("Start point"), _("End Point")], None, float, float], 263 "def_vals": [False, 0, "new point", 0, 0] 264 } 265 266 # registration graphics for drawing 267 self.pointsToDraw = self.mapWin.RegisterGraphicsToDraw( 268 graphicsType="point", setStatusFunc=self.SetPointStatus) 269 270 self.SetPointDrawSettings() 271 272 self.AddPoint() 273 self.AddPoint() 274 275 self.SetPointData(0, {'use': True, 'type': 1}) 276 self.SetPointData(1, {'use': True, 'type': 2}) 277 278 self.selected = 0 279 280 def __del__(self): 281 self.CleanUp() 282 283 def CleanUp(self): 284 self.mapWin.UnregisterGraphicsToDraw(self.pointsToDraw) 285 286 if self.handlerRegistered: 287 self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN, 288 self.OnMapClickHandler) 289 290 def SetSnapping(self, activate): 291 self.snapping = activate 292 293 def GetSnapping(self): 294 return self.snapping 295 296 def AddPoint(self): 297 298 self.pointsToDraw.AddItem( 299 coords=( 300 self.cols["def_vals"][3], 301 self.cols["def_vals"][4])) 302 self.data.append(self.cols["def_vals"][:]) 303 304 self.pointsChanged.emit(method="AddPoint", kwargs={}) 305 306 def DeletePoint(self, pt_id): 307 item = self.pointsToDraw.GetItem(pt_id) 308 if item: 309 self.pointsToDraw.DeleteItem(item) 310 self.data.pop(pt_id) 311 312 self.pointsChanged.emit(method="DeletePoint", kwargs={"pt_id": pt_id}) 313 314 def SetPoints(self, pts_data): 315 316 for item in self.pointsToDraw.GetAllItems(): 317 self.pointsToDraw.DeleteItem(item) 318 319 self.data = [] 320 for pt_data in pts_data: 321 pt_data_list = self._ptDataToList(pt_data) 322 self.data.append(pt_data_list) 323 self.pointsToDraw.AddItem( 324 coords=(pt_data_list[3], pt_data_list[4])) 325 326 self.pointsChanged.emit( 327 method="SetPoints", kwargs={ 328 "pts_data": pts_data}) 329 330 def SetPointData(self, pt_id, data): 331 for col, v in six.iteritems(data): 332 if col == 'use': 333 continue 334 335 idx = self.cols["name"].index(col) 336 self.data[pt_id][idx] = v 337 338 # if type is changed checked columns must be recalculated by _usePoint 339 if 'type' in data and 'use' not in data: 340 data["use"] = self.GetPointData(pt_id)['use'] 341 342 if 'use' in data: 343 if self._usePoint(pt_id, data["use"]) == -1: 344 data["use"] = False 345 idx = self.cols["name"].index("use") 346 self.data[pt_id][idx] = data["use"] 347 348 self.pointsChanged.emit( 349 method="SetPointData", kwargs={ 350 "pt_id": pt_id, "data": data}) 351 352 def GetPointData(self, pt_id): 353 return self._ptListDataToPtData(self.data[pt_id]) 354 355 def GetPointsCount(self): 356 return len(self.data) 357 358 def SetPointStatus(self, item, itemIndex): 359 """Before point is drawn, decides properties of drawing style""" 360 analysis, valid = self.an_params.GetParam("analysis") 361 cats = self.an_data[analysis]["cmdParams"]["cats"] 362 363 if itemIndex == self.selected: 364 wxPen = "selected" 365 elif not self.data[itemIndex][0]: 366 wxPen = "unused" 367 item.hide = False 368 elif len(cats) > 1: 369 idx = self.data[itemIndex][1] 370 if idx == 2: # End/To/Sink point 371 wxPen = "used2cat" 372 else: 373 wxPen = "used1cat" 374 else: 375 wxPen = "used1cat" 376 377 item.SetPropertyVal('label', str(itemIndex + 1)) 378 item.SetPropertyVal('penName', wxPen) 379 380 def SetSelected(self, pt_id): 381 self.selected = pt_id 382 self.pointsChanged.emit(method="SetSelected", kwargs={"pt_id": pt_id}) 383 384 def GetSelected(self): 385 return self.selected 386 387 def SetPointDrawSettings(self): 388 """Set settings for drawing of points""" 389 ptSize = int( 390 UserSettings.Get( 391 group='vnet', 392 key='point_symbol', 393 subkey='point_size')) 394 self.pointsToDraw.SetPropertyVal("size", ptSize) 395 396 colors = UserSettings.Get(group='vnet', key='point_colors') 397 ptWidth = int( 398 UserSettings.Get( 399 group='vnet', 400 key='point_symbol', 401 subkey='point_width')) 402 403 textProp = self.pointsToDraw.GetPropertyVal("text") 404 textProp["font"].SetPointSize(ptSize + 2) 405 406 for colKey, col in six.iteritems(colors): 407 pen = self.pointsToDraw.GetPen(colKey) 408 if pen: 409 pen.SetColour(wx.Colour(col[0], col[1], col[2], 255)) 410 pen.SetWidth(ptWidth) 411 else: 412 self.pointsToDraw.AddPen( 413 colKey, 414 wx.Pen( 415 colour=wx.Colour( 416 col[0], 417 col[1], 418 col[2], 419 255), 420 width=ptWidth)) 421 422 def ParametersChanged(self, method, kwargs): 423 if "analysis" in list(kwargs["changed_params"].keys()): 424 self._updateTypeCol() 425 426 if self.an_params.GetParam("analysis")[0] == "v.net.path": 427 self._vnetPathUpdateUsePoints(None) 428 429 def _updateTypeCol(self): 430 """Rename category values when module is changed. Expample: Start point -> Sink point""" 431 colValues = [""] 432 analysis, valid = self.an_params.GetParam("analysis") 433 anParamsCats = self.an_data[analysis]["cmdParams"]["cats"] 434 435 for ptCat in anParamsCats: 436 colValues.append(ptCat[1]) 437 438 type_idx = self.cols["name"].index("type") 439 self.cols['type'][type_idx] = colValues 440 441 def _ptDataToList(self, pt_data): 442 443 pt_list_data = [None] * len(self.cols['name']) 444 445 for k, val in six.iteritems(pt_data): 446 pt_list_data[self.cols["name"].index(k)] = val 447 448 return pt_list_data 449 450 def _ptListDataToPtData(self, pt_list_data): 451 452 pt_data = {} 453 for i, val in enumerate(pt_list_data): 454 pt_data[self.cols["name"][i]] = val 455 456 return pt_data 457 458 def _usePoint(self, pt_id, use): 459 """Item is checked/unchecked""" 460 analysis, valid = self.an_params.GetParam("analysis") 461 cats = self.an_data[analysis]["cmdParams"]["cats"] 462 # TODO move 463 # if self.updateMap: 464 # up_map_evt = gUpdateMap(render = False, renderVector = False) 465 # wx.PostEvent(self.dialog.mapWin, up_map_evt) 466 467 if len(cats) <= 1: 468 return 0 469 470 use_idx = self.cols["name"].index("use") 471 checkedVal = self.data[pt_id][1] 472 473 # point without given type cannot be selected 474 if checkedVal == 0: 475 self.data[pt_id][use_idx] = False 476 self.pointsChanged.emit( 477 method="SetPointData", kwargs={ 478 "pt_id": pt_id, "data": { 479 "use": False}}) 480 return -1 481 482 if analysis == "v.net.path" and use: 483 self._vnetPathUpdateUsePoints(pt_id) 484 485 def _vnetPathUpdateUsePoints(self, checked_pt_id): 486 487 alreadyChecked = [] 488 489 type_idx = self.cols["name"].index("type") 490 use_idx = self.cols["name"].index("use") 491 492 if checked_pt_id is not None: 493 checkedKey = checked_pt_id 494 alreadyChecked.append(self.data[checked_pt_id][type_idx]) 495 else: 496 checkedKey = -1 497 498 for iKey, dt in enumerate(self.data): 499 pt_type = dt[type_idx] 500 501 if ((pt_type in alreadyChecked and checkedKey != iKey) 502 or pt_type == 0) and self.data[iKey][use_idx]: 503 self.data[iKey][use_idx] = False 504 self.pointsChanged.emit( 505 method="SetPointData", kwargs={ 506 "pt_id": iKey, "data": { 507 "use": False}}) 508 elif self.data[iKey][use_idx]: 509 alreadyChecked.append(pt_type) 510 511 def EditPointMode(self, activate): 512 """Registers/unregisters mouse handler into map window""" 513 514 if activate == self.handlerRegistered: 515 return 516 517 if activate: 518 self.mapWin.RegisterMouseEventHandler(wx.EVT_LEFT_DOWN, 519 self.OnMapClickHandler, 520 'cross') 521 self.handlerRegistered = True 522 else: 523 self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN, 524 self.OnMapClickHandler) 525 self.handlerRegistered = False 526 527 self.pointsChanged.emit( 528 method="EditMode", kwargs={ 529 "activated": activate}) 530 531 def IsEditPointModeActive(self): 532 return self.handlerRegistered 533 534 def OnMapClickHandler(self, event): 535 """Take coordinates from map window""" 536 # TODO update snapping after input change 537 if event == 'unregistered': 538 self.handlerRegistered = False 539 return 540 541 if not self.data: 542 self.AddPoint() 543 544 e, n = self.mapWin.GetLastEN() 545 546 if self.snapping: 547 548 # compute threshold 549 snapTreshPix = int(UserSettings.Get(group='vnet', 550 key='other', 551 subkey='snap_tresh')) 552 res = max( 553 self.mapWin.Map.region['nsres'], 554 self.mapWin.Map.region['ewres']) 555 snapTreshDist = snapTreshPix * res 556 557 params, err_params, flags = self.an_params.GetParams() 558 vectMap = params["input"] 559 560 if "input" in err_params: 561 msg = _("new point") 562 563 coords = SnapToNode(e, n, snapTreshDist, vectMap) 564 if coords: 565 e = coords[0] 566 n = coords[1] 567 568 msg = ("snapped to node") 569 else: 570 msg = _("new point") 571 572 else: 573 msg = _("new point") 574 575 self.SetPointData(self.selected, 576 {'topology': msg, 577 'e': e, 578 'n': n}) 579 580 self.pointsToDraw.GetItem(self.selected).SetCoords([e, n]) 581 582 if self.selected == len(self.data) - 1: 583 self.SetSelected(0) 584 else: 585 self.SetSelected(self.GetSelected() + 1) 586 587 def GetColumns(self, only_relevant=True): 588 589 cols_data = deepcopy(self.cols) 590 591 hidden_cols = [] 592 hidden_cols.append(self.cols["name"].index("e")) 593 hidden_cols.append(self.cols["name"].index("n")) 594 595 analysis, valid = self.an_params.GetParam("analysis") 596 if only_relevant and len(self.an_data[analysis][ 597 "cmdParams"]["cats"]) <= 1: 598 hidden_cols.append(self.cols["name"].index("type")) 599 600 i_red = 0 601 hidden_cols.sort() 602 for idx in hidden_cols: 603 for dt in six.itervalues(cols_data): 604 dt.pop(idx - i_red) 605 i_red += 1 606 607 return cols_data 608 609 610class VNETAnalysisParameters: 611 612 def __init__(self, an_props): 613 614 self.an_props = an_props 615 616 self.params = {"analysis": self.an_props.used_an[0], 617 "input": "", 618 "arc_layer": "", 619 "node_layer": "", 620 "arc_column": "", 621 "arc_backward_column": "", 622 "node_column": "", 623 "turn_layer": "", 624 "turn_cat_layer": "", 625 "iso_lines": "", # TODO check validity 626 "max_dist": 0} # TODO check validity 627 628 self.flags = {"t": False} 629 630 self.parametersChanged = Signal( 631 'VNETAnalysisParameters.parametersChanged') 632 633 def SetParams(self, params, flags): 634 635 changed_params = {} 636 for p, v in six.iteritems(params): 637 if p == "analysis" and v not in self.an_props.used_an: 638 continue 639 640 if p == "input": 641 mapName, mapSet = ParseMapStr(v) 642 v = mapName + "@" + mapSet 643 644 if p in self.params: 645 if isinstance(v, str): 646 v = v.strip() 647 648 self.params[p] = v 649 changed_params[p] = v 650 651 changed_flags = {} 652 for p, v in six.iteritems(flags): 653 if p in self.flags: 654 self.flags[p] = v 655 changed_flags[p] = v 656 657 self.parametersChanged.emit( 658 method="SetParams", 659 kwargs={ 660 "changed_params": changed_params, 661 "changed_flags": changed_flags}) 662 663 return changed_params, changed_flags 664 665 def GetParam(self, param): 666 667 invParams = [] 668 if param in [ 669 "input", "arc_layer", "node_layer", "arc_column", 670 "arc_backward_column", "node_column", "turn_layer", 671 "turn_cat_layer"]: 672 invParams = self._getInvalidParams(self.params) 673 674 if invParams: 675 return self.params[param], False 676 677 return self.params[param], True 678 679 def GetParams(self): 680 681 invParams = self._getInvalidParams(self.params) 682 return self.params, invParams, self.flags 683 684 def _getInvalidParams(self, params): 685 """Check of analysis input data for invalid values (Parameters tab)""" 686 # dict of invalid values {key from self.itemData (comboboxes from 687 # Parameters tab) : invalid value} 688 invParams = [] 689 690 # check vector map 691 if params["input"]: 692 mapName, mapSet = params["input"].split("@") 693 if mapSet in grass.list_grouped('vector'): 694 vectMaps = grass.list_grouped('vector')[mapSet] 695 696 if not params["input"] or mapName not in vectMaps: 697 invParams = list(params.keys())[:] 698 return invParams 699 700 # check arc/node layer 701 layers = utils.GetVectorNumberOfLayers(params["input"]) 702 703 for l in ['arc_layer', 'node_layer', 'turn_layer', 'turn_cat_layer']: 704 if not layers or params[l] not in layers: 705 invParams.append(l) 706 707 dbInfo = VectorDBInfo(params["input"]) 708 709 try: 710 table = dbInfo.GetTable(int(params["arc_layer"])) 711 columnchoices = dbInfo.GetTableDesc(table) 712 except (KeyError, ValueError): 713 table = None 714 715 # check costs columns 716 for col in ["arc_column", "arc_backward_column", "node_column"]: 717 718 if col == "node_column": 719 try: 720 table = dbInfo.GetTable(int(params["node_layer"])) 721 columnchoices = dbInfo.GetTableDesc(table) 722 except (KeyError, ValueError): 723 table = None 724 725 if not table or not params[col] in list(columnchoices.keys()): 726 invParams.append(col) 727 continue 728 729 if columnchoices[ 730 params[col]]['type'] not in [ 731 'integer', 'double precision']: 732 invParams.append(col) 733 continue 734 735 return invParams 736 737 738class VNETAnalysesProperties: 739 740 def __init__(self): 741 """Initializes parameters for different v.net.* modules """ 742 # initialization of v.net.* analysis parameters (data which 743 # characterizes particular analysis) 744 745 self.attrCols = { 746 'arc_column': { 747 "label": _("Arc forward/both direction(s) cost column:"), 748 "name": _("arc forward/both") 749 }, 750 'arc_backward_column': { 751 "label": _("Arc backward direction cost column:"), 752 "name": _("arc backward") 753 }, 754 'acolumn': { 755 "label": _("Arcs' cost column (for both directions):"), 756 "name": _("arc"), 757 "inputField": 'arc_column', 758 }, 759 'node_column': { 760 "label": _("Node cost column:"), 761 "name": _("node") 762 } 763 } 764 765 self.vnetProperties = { 766 "v.net.path": { 767 "label": _("Shortest path %s") % "(v.net.path)", 768 "cmdParams": { 769 "cats": [ 770 ["st_pt", _("Start point")], 771 ["end_pt", _("End point")] 772 ], 773 "cols": [ 774 'arc_column', 775 'arc_backward_column', 776 'node_column' 777 ], 778 }, 779 "resultProps": { 780 "singleColor": None, 781 "dbMgr": True # TODO delete this property, this information can be get from result 782 }, 783 "turns_support": True 784 }, 785 786 "v.net.salesman": { 787 "label": _("Traveling salesman %s") % "(v.net.salesman)", 788 "cmdParams": { 789 "cats": [["center_cats", None]], 790 "cols": [ 791 'arc_column', 792 'arc_backward_column' 793 ], 794 }, 795 "resultProps": { 796 "singleColor": None, 797 "dbMgr": False 798 }, 799 "turns_support": True 800 801 }, 802 "v.net.flow": { 803 "label": _("Maximum flow %s") % "(v.net.flow)", 804 "cmdParams": { 805 "cats": [ 806 ["source_cats", _("Source point")], 807 ["sink_cats", _("Sink point")] 808 ], 809 "cols": [ 810 'arc_column', 811 'arc_backward_column', 812 'node_column' 813 ] 814 }, 815 "resultProps": { 816 "attrColColor": "flow", 817 "dbMgr": True 818 }, 819 "turns_support": False 820 }, 821 "v.net.alloc": { 822 "label": _("Subnets for nearest centers %s") % "(v.net.alloc)", 823 "cmdParams": { 824 "cats": [["center_cats", None]], 825 "cols": [ 826 'arc_column', 827 'arc_backward_column', 828 'node_column' 829 ] 830 }, 831 "resultProps": { 832 "catColor": None, 833 "dbMgr": False 834 }, 835 "turns_support": True 836 }, 837 "v.net.steiner": { 838 "label": _("Steiner tree for the network and given terminals %s") % "(v.net.steiner)", 839 "cmdParams": { 840 "cats": [["terminal_cats", None]], 841 "cols": [ 842 'acolumn', 843 ] 844 }, 845 "resultProps": { 846 "singleColor": None, 847 "dbMgr": False 848 }, 849 "turns_support": True 850 }, 851 "v.net.distance": { 852 "label": _("Shortest distance via the network %s") % "(v.net.distance)", 853 "cmdParams": { 854 "cats": [ 855 ["from_cats", "From point"], 856 ["to_cats", "To point"] 857 ], 858 "cols": [ 859 'arc_column', 860 'arc_backward_column', 861 'node_column' 862 ], 863 }, 864 "resultProps": { 865 "catColor": None, 866 "dbMgr": True 867 }, 868 "turns_support": False 869 }, 870 "v.net.iso": { 871 "label": _("Cost isolines %s") % "(v.net.iso)", 872 "cmdParams": { 873 "cats": [["center_cats", None]], 874 "cols": [ 875 'arc_column', 876 'arc_backward_column', 877 'node_column' 878 ] 879 }, 880 "resultProps": { 881 "catColor": None, 882 "dbMgr": False 883 }, 884 "turns_support": True 885 } 886 } 887 888 self.used_an = ["v.net.path", 889 "v.net.salesman", 890 "v.net.flow", 891 "v.net.alloc", 892 "v.net.distance", 893 "v.net.iso", 894 #"v.net.steiner" 895 ] 896 897 for an in list(self.vnetProperties.keys()): 898 if an not in self.used_an: 899 del self.vnetProperties[an] 900 continue 901 902 cols = self.vnetProperties[an]["cmdParams"]["cols"] 903 self.vnetProperties[an]["cmdParams"]["cols"] = {} 904 for c in cols: 905 self.vnetProperties[an]["cmdParams"][ 906 "cols"][c] = self.attrCols[c] 907 908 def has_key(self, key): 909 return key in self.vnetProperties 910 911 def __getitem__(self, key): 912 return self.vnetProperties[key] 913 914 def GetRelevantParams(self, analysis): 915 916 if analysis not in self.vnetProperties: 917 return None 918 919 relevant_params = ["input", "arc_layer", "node_layer"] 920 921 if self.vnetProperties[analysis]["turns_support"]: 922 relevant_params += ["turn_layer", "turn_cat_layer"] 923 924 cols = self.vnetProperties[analysis]["cmdParams"]["cols"] 925 926 for col, v in six.iteritems(cols): 927 if "inputField" in col: 928 colInptF = v["inputField"] 929 else: 930 colInptF = col 931 relevant_params.append(colInptF) 932 933 return relevant_params 934 935 936class VNETTmpVectMaps: 937 """Class which creates, stores and destroys all tmp maps created during analysis""" 938 939 def __init__(self, parent, mapWin): 940 self.tmpMaps = [] # temporary maps 941 self.parent = parent 942 self.mapWin = mapWin 943 944 def AddTmpVectMap(self, mapName, msg): 945 """New temporary map 946 947 :return: instance of VectMap representing temporary map 948 """ 949 currMapSet = grass.gisenv()['MAPSET'] 950 tmpMap = grass.find_file(name=mapName, 951 element='vector', 952 mapset=currMapSet) 953 954 fullName = tmpMap["fullname"] 955 # map already exists 956 if fullName: 957 # TODO move dialog out of class, AddTmpVectMap(self, mapName, 958 # overvrite = False) 959 dlg = wx.MessageDialog(parent=self.parent, 960 message=msg, 961 caption=_("Overwrite map layer"), 962 style=wx.YES_NO | wx.NO_DEFAULT | 963 wx.ICON_QUESTION | wx.CENTRE) 964 965 ret = dlg.ShowModal() 966 dlg.Destroy() 967 968 if ret == wx.ID_NO: 969 return None 970 else: 971 fullName = mapName + "@" + currMapSet 972 973 newVectMap = VectMap(self.mapWin, fullName) 974 self.tmpMaps.append(newVectMap) 975 976 return newVectMap 977 978 def HasTmpVectMap(self, vectMapName): 979 """ 980 :param: vectMapName name of vector map 981 982 :return: True if it contains the map 983 :return: False if not 984 """ 985 986 mapValSpl = vectMapName.strip().split("@") 987 if len(mapValSpl) > 1: 988 mapSet = mapValSpl[1] 989 else: 990 mapSet = grass.gisenv()['MAPSET'] 991 mapName = mapValSpl[0] 992 fullName = mapName + "@" + mapSet 993 994 for vectTmpMap in self.tmpMaps: 995 if vectTmpMap.GetVectMapName() == fullName: 996 return True 997 return False 998 999 def GetTmpVectMap(self, vectMapName): 1000 """Get instance of VectMap with name vectMapName""" 1001 for vectMap in self.tmpMaps: 1002 if vectMap.GetVectMapName() == vectMapName.strip(): 1003 return vectMap 1004 return None 1005 1006 def RemoveFromTmpMaps(self, vectMap): 1007 """Temporary map is removed from the class instance however it is not deleted 1008 1009 :param vectMap: instance of VectMap class to be removed 1010 1011 :return: True if was removed 1012 :return: False if does not contain the map 1013 """ 1014 try: 1015 self.tmpMaps.remove(vectMap) 1016 return True 1017 except ValueError: 1018 return False 1019 1020 def DeleteTmpMap(self, vectMap): 1021 """Temporary map is removed from the class and it is deleted 1022 1023 :param vectMap: instance of VectMap class to be deleted 1024 1025 :return: True if was removed 1026 :return: False if does not contain the map 1027 """ 1028 if vectMap: 1029 vectMap.DeleteRenderLayer() 1030 RunCommand('g.remove', flags='f', type='vector', 1031 name=vectMap.GetVectMapName()) 1032 self.RemoveFromTmpMaps(vectMap) 1033 return True 1034 return False 1035 1036 def DeleteAllTmpMaps(self): 1037 """Delete all temporary maps in the class""" 1038 update = False 1039 for tmpMap in self.tmpMaps: 1040 RunCommand('g.remove', flags='f', type='vector', 1041 name=tmpMap.GetVectMapName()) 1042 if tmpMap.DeleteRenderLayer(): 1043 update = True 1044 return update 1045 1046 1047class VectMap: 1048 """Represents map 1049 It can check if it was modified or render it 1050 """ 1051 1052 def __init__(self, mapWin, fullName): 1053 self.fullName = fullName 1054 self.mapWin = mapWin 1055 self.renderLayer = None 1056 self.modifTime = None # time, for modification check 1057 1058 def __del__(self): 1059 1060 self.DeleteRenderLayer() 1061 1062 def AddRenderLayer(self, cmd=None, colorsCmd=None): 1063 """Add map from map window layers to render """ 1064 1065 if not self.mapWin: 1066 return False 1067 1068 existsMap = grass.find_file(name=self.fullName, 1069 element='vector', 1070 mapset=grass.gisenv()['MAPSET']) 1071 1072 if not existsMap["name"]: 1073 self.DeleteRenderLayer() 1074 return False 1075 1076 if not cmd: 1077 cmd = [] 1078 cmd.insert(0, 'd.vect') 1079 cmd.append('map=%s' % self.fullName) 1080 1081 if self.renderLayer: 1082 self.DeleteRenderLayer() 1083 1084 if colorsCmd: 1085 colorsCmd.append('map=%s' % self.fullName) 1086 layerStyleVnetColors = cmdlist_to_tuple(colorsCmd) 1087 1088 RunCommand(layerStyleVnetColors[0], 1089 **layerStyleVnetColors[1]) 1090 1091 self.renderLayer = self.mapWin.Map.AddLayer( 1092 ltype="vector", command=cmd, name=self.fullName, active=True, 1093 opacity=1.0, render=False, pos=-1) 1094 return True 1095 1096 def DeleteRenderLayer(self): 1097 """Remove map from map window layers to render""" 1098 if not self.mapWin: 1099 return False 1100 1101 if self.renderLayer: 1102 self.mapWin.Map.DeleteLayer(self.renderLayer) 1103 self.renderLayer = None 1104 return True 1105 return False 1106 1107 def GetRenderLayer(self): 1108 return self.renderLayer 1109 1110 def GetVectMapName(self): 1111 return self.fullName 1112 1113 def SaveVectMapState(self): 1114 """Save modification time for vector map""" 1115 self.modifTime = self.GetLastModified() 1116 1117 def VectMapState(self): 1118 """Checks if map was modified 1119 1120 :return: -1 - if no modification time was saved 1121 :return: 0 - if map was modified 1122 :return: 1 - if map was not modified 1123 """ 1124 if self.modifTime is None: 1125 return -1 1126 if self.modifTime != self.GetLastModified(): 1127 return 0 1128 return 1 1129 1130 def GetLastModified(self): 1131 """Get modification time 1132 1133 :return: MAP DATE time string from vector map head file 1134 """ 1135 1136 mapValSpl = self.fullName.split("@") 1137 mapSet = mapValSpl[1] 1138 mapName = mapValSpl[0] 1139 1140 headPath = os.path.join(grass.gisenv()['GISDBASE'], 1141 grass.gisenv()['LOCATION_NAME'], 1142 mapSet, 1143 "vector", 1144 mapName, 1145 "head") 1146 try: 1147 head = open(headPath, 'r') 1148 for line in head.readlines(): 1149 i = line.find('MAP DATE:', ) 1150 if i == 0: 1151 head.close() 1152 return line.split(':', 1)[1].strip() 1153 1154 head.close() 1155 return "" 1156 except IOError: 1157 return "" 1158 1159 1160class History: 1161 """Class which reads and saves history data (based on gui.core.settings Settings class file save/load) 1162 1163 .. todo:: 1164 Maybe it could be useful for other GRASS wxGUI tools. 1165 """ 1166 1167 def __init__(self): 1168 1169 # max number of steps in history (zero based) 1170 self.maxHistSteps = 3 1171 # current history step 1172 self.currHistStep = 0 1173 # number of steps saved in history 1174 self.histStepsNum = 0 1175 1176 # dict contains data saved in history for current history step 1177 self.currHistStepData = {} 1178 1179 # buffer for data to be saved into history 1180 self.newHistStepData = {} 1181 1182 self.histFile = grass.tempfile() 1183 1184 # key/value separator 1185 self.sep = ';' 1186 1187 def __del__(self): 1188 try_remove(self.histFile) 1189 1190 def GetNext(self): 1191 """Go one step forward in history""" 1192 self.currHistStep -= 1 1193 self.currHistStepData.clear() 1194 self.currHistStepData = self._getHistStepData(self.currHistStep) 1195 1196 return self.currHistStepData 1197 1198 def GetPrev(self): 1199 """Go one step back in history""" 1200 self.currHistStep += 1 1201 self.currHistStepData.clear() 1202 self.currHistStepData = self._getHistStepData(self.currHistStep) 1203 1204 return self.currHistStepData 1205 1206 def GetStepsNum(self): 1207 """Get number of steps saved in history""" 1208 return self.histStepsNum 1209 1210 def GetCurrHistStep(self): 1211 """Get current history step""" 1212 return self.currHistStep 1213 1214 def Add(self, key, subkey, value): 1215 """Add new data into buffer""" 1216 if key not in self.newHistStepData: 1217 self.newHistStepData[key] = {} 1218 1219 if isinstance(subkey, list): 1220 if subkey[0] not in self.newHistStepData[key]: 1221 self.newHistStepData[key][subkey[0]] = {} 1222 self.newHistStepData[key][subkey[0]][subkey[1]] = value 1223 else: 1224 self.newHistStepData[key][subkey] = value 1225 1226 def SaveHistStep(self): 1227 """Create new history step with data in buffer""" 1228 self.maxHistSteps = UserSettings.Get(group='vnet', 1229 key='other', 1230 subkey='max_hist_steps') 1231 self.currHistStep = 0 1232 1233 newHistFile = grass.tempfile() 1234 newHist = open(newHistFile, "w") 1235 1236 self._saveNewHistStep(newHist) 1237 1238 oldHist = open(self.histFile) 1239 removedHistData = self._savePreviousHist(newHist, oldHist) 1240 1241 oldHist.close() 1242 newHist.close() 1243 try_remove(self.histFile) 1244 self.histFile = newHistFile 1245 1246 self.newHistStepData.clear() 1247 1248 return removedHistData 1249 1250 def _savePreviousHist(self, newHist, oldHist): 1251 """Save previous history into new file""" 1252 newHistStep = False 1253 removedHistData = {} 1254 newHistStepsNum = self.histStepsNum 1255 1256 for line in oldHist.readlines(): 1257 if not line.strip(): 1258 newHistStep = True 1259 newHistStepsNum += 1 1260 continue 1261 1262 if newHistStep: 1263 newHistStep = False 1264 1265 line = line.split("=") 1266 line[1] = str(newHistStepsNum) 1267 line = "=".join(line) 1268 1269 if newHistStepsNum >= self.maxHistSteps: 1270 removedHistStep = removedHistData[line] = {} 1271 continue 1272 else: 1273 newHist.write('%s%s%s' % ('\n', line, '\n')) 1274 self.histStepsNum = newHistStepsNum 1275 else: 1276 if newHistStepsNum >= self.maxHistSteps: 1277 self._parseLine(line, removedHistStep) 1278 else: 1279 newHist.write('%s' % line) 1280 1281 return removedHistData 1282 1283 def _saveNewHistStep(self, newHist): 1284 """Save buffer (new step) data into file""" 1285 newHist.write('%s%s%s' % ('\n', "history step=0", '\n')) 1286 for key in list(self.newHistStepData.keys()): 1287 subkeys = list(self.newHistStepData[key].keys()) 1288 newHist.write('%s%s' % (key, self.sep)) 1289 for idx in range(len(subkeys)): 1290 value = self.newHistStepData[key][subkeys[idx]] 1291 if isinstance(value, dict): 1292 if idx > 0: 1293 newHist.write('%s%s%s' % ('\n', key, self.sep)) 1294 newHist.write('%s%s' % (subkeys[idx], self.sep)) 1295 kvalues = list(self.newHistStepData[key][subkeys[idx]].keys()) 1296 srange = range(len(kvalues)) 1297 for sidx in srange: 1298 svalue = self._parseValue( 1299 self.newHistStepData[key][ 1300 subkeys[idx]][ 1301 kvalues[sidx]]) 1302 newHist.write( 1303 '%s%s%s' % 1304 (kvalues[sidx], self.sep, svalue)) 1305 if sidx < len(kvalues) - 1: 1306 newHist.write('%s' % self.sep) 1307 else: 1308 if idx > 0 and isinstance( 1309 self.newHistStepData[key][subkeys[idx - 1]], 1310 dict): 1311 newHist.write('%s%s%s' % ('\n', key, self.sep)) 1312 value = self._parseValue( 1313 self.newHistStepData[key][subkeys[idx]]) 1314 newHist.write('%s%s%s' % (subkeys[idx], self.sep, value)) 1315 if idx < len(subkeys) - 1 and not isinstance( 1316 self.newHistStepData[key][subkeys[idx + 1]], 1317 dict): 1318 newHist.write('%s' % self.sep) 1319 newHist.write('\n') 1320 self.histStepsNum = 0 1321 1322 def _parseValue(self, value, read=False): 1323 """Parse value""" 1324 if read: # -> read data (cast values) 1325 1326 if value: 1327 if value[ 1328 0] == '[' and value[-1] == ']': # TODO, possible wrong interpretation 1329 value = value[1:-1].split(',') 1330 value = map(self._castValue, value) 1331 return value 1332 1333 if value == 'True': 1334 value = True 1335 elif value == 'False': 1336 value = False 1337 elif value == 'None': 1338 value = None 1339 elif ':' in value: # -> color 1340 try: 1341 value = tuple(map(int, value.split(':'))) 1342 except ValueError: # -> string 1343 pass 1344 else: 1345 try: 1346 value = int(value) 1347 except ValueError: 1348 try: 1349 value = float(value) 1350 except ValueError: 1351 pass 1352 else: # -> write data 1353 if isinstance(value, type(())): # -> color 1354 value = str(value[0]) + ':' +\ 1355 str(value[1]) + ':' + \ 1356 str(value[2]) 1357 1358 return value 1359 1360 def _castValue(self, value): 1361 """Cast value""" 1362 try: 1363 value = int(value) 1364 except ValueError: 1365 try: 1366 value = float(value) 1367 except ValueError: 1368 value = value[1:-1] 1369 1370 return value 1371 1372 def _getHistStepData(self, histStep): 1373 """Load data saved in history step""" 1374 hist = open(self.histFile) 1375 histStepData = {} 1376 1377 newHistStep = False 1378 isSearchedHistStep = False 1379 for line in hist.readlines(): 1380 1381 if not line.strip() and isSearchedHistStep: 1382 break 1383 elif not line.strip(): 1384 newHistStep = True 1385 continue 1386 elif isSearchedHistStep: 1387 self._parseLine(line, histStepData) 1388 1389 if newHistStep: 1390 line = line.split("=") 1391 if int(line[1]) == histStep: 1392 isSearchedHistStep = True 1393 newHistStep = False 1394 1395 hist.close() 1396 return histStepData 1397 1398 def _parseLine(self, line, histStepData): 1399 """Parse line in file with history""" 1400 line = line.rstrip('%s' % os.linesep).split(self.sep) 1401 key = line[0] 1402 kv = line[1:] 1403 idx = 0 1404 subkeyMaster = None 1405 if len(kv) % 2 != 0: # multiple (e.g. nviz) 1406 subkeyMaster = kv[0] 1407 del kv[0] 1408 idx = 0 1409 while idx < len(kv): 1410 if subkeyMaster: 1411 subkey = [subkeyMaster, kv[idx]] 1412 else: 1413 subkey = kv[idx] 1414 value = kv[idx + 1] 1415 value = self._parseValue(value, read=True) 1416 if key not in histStepData: 1417 histStepData[key] = {} 1418 1419 if isinstance(subkey, list): 1420 if subkey[0] not in histStepData[key]: 1421 histStepData[key][subkey[0]] = {} 1422 histStepData[key][subkey[0]][subkey[1]] = value 1423 else: 1424 histStepData[key][subkey] = value 1425 idx += 2 1426 1427 def DeleteNewHistStepData(self): 1428 """Delete buffer data for new history step""" 1429 self.newHistStepData.clear() 1430 1431 1432class VNETGlobalTurnsData: 1433 """Turn Data""" 1434 1435 def __init__(self): 1436 # Definition of four basic directions 1437 self.turn_data = [ 1438 ["Straight", DegreesToRadians(-30), DegreesToRadians(+30), 0.0], 1439 ["Right Turn", DegreesToRadians(+30), DegreesToRadians(+150), 0.0], 1440 ["Reverse", DegreesToRadians(+150), DegreesToRadians(-150), 0.0], 1441 ["Left Turn", DegreesToRadians(-150), DegreesToRadians(-30), 0.0] 1442 ] 1443 1444 def GetData(self): 1445 data = [] 1446 for ival in self.turn_data: 1447 data.append(ival[1:]) 1448 1449 return data 1450 1451 def GetValue(self, line, col): 1452 return self.turn_data[line][col] 1453 1454 def GetLinesCount(self): 1455 return len(self.turn_data) 1456 1457 def SetValue(self, value, line, col): 1458 self.DataValidator(line, col, value) 1459 self.turn_data[line][col] = value 1460 1461 def SetUTurns(self, value): 1462 """Checked if checeBox is checed""" 1463 useUTurns = value 1464 1465 def AppendRow(self, values): 1466 self.turn_data.append(values) 1467 1468 def InsertRow(self, line, values): 1469 self.turn_data.insert(line, values) 1470 1471 def PopRow(self, values): 1472 self.RemoveDataValidator(values) 1473 self.turn_data.pop(values) 1474 1475 def DataValidator(self, row, col, value): 1476 """Angle recalculation due to value changing""" 1477 1478 if col not in [1, 2]: 1479 return 1480 1481 if col == 1: 1482 new_from_angle = value 1483 old_from_angle = self.turn_data[row][1] 1484 new_to_angle = self.turn_data[row][2] 1485 if self.IsInInterval(old_from_angle, new_to_angle, new_from_angle): 1486 1487 prev_row = row - 1 1488 if prev_row == -1: 1489 prev_row = len(self.turn_data) - 1 1490 self.turn_data[prev_row][2] = new_from_angle 1491 return 1492 1493 if col == 2: 1494 new_to_angle = value 1495 old_to_angle = self.turn_data[row][2] 1496 new_from_angle = self.turn_data[row][1] 1497 if self.IsInInterval(new_from_angle, old_to_angle, new_to_angle): 1498 1499 next_row = row + 1 1500 if len(self.turn_data) == next_row: 1501 next_row = 0 1502 self.turn_data[next_row][1] = new_to_angle 1503 return 1504 1505 inside_new = [] 1506 overlap_new_from = [] 1507 overlap_new_to = [] 1508 1509 for i in range(self.GetLinesCount()): 1510 if i == row: 1511 continue 1512 from_angle = self.turn_data[i][1] 1513 is_in_from = self.IsInInterval( 1514 new_from_angle, new_to_angle, from_angle) 1515 1516 to_angle = self.turn_data[i][2] 1517 is_in_to = self.IsInInterval( 1518 new_from_angle, new_to_angle, to_angle) 1519 1520 if is_in_from and is_in_to: 1521 inside_new.append(i) 1522 if is_in_from: 1523 overlap_new_to.append(i) 1524 if is_in_to: 1525 overlap_new_from.append(i) 1526 1527 for i_row in overlap_new_from: 1528 self.turn_data[i_row][2] = new_from_angle 1529 1530 for i_row in overlap_new_to: 1531 self.turn_data[i_row][1] = new_to_angle 1532 1533 for i_row in inside_new: 1534 if col == 1: 1535 angle = new_from_angle 1536 else: 1537 angle = new_to_angle 1538 1539 self.turn_data[i_row][1] = angle 1540 self.turn_data[i_row][2] = angle 1541 1542 def RemoveDataValidator(self, row): 1543 """Angle recalculation due to direction remove""" 1544 if row == 0: 1545 prev_row = self.GetLinesCount() - 1 1546 else: 1547 prev_row = row - 1 1548 1549 remove_to_angle = self.turn_data[row][2] 1550 self.turn_data[prev_row][2] = remove_to_angle 1551 1552 def IsInInterval(self, from_angle, to_angle, angle): 1553 """Test if a direction includes or not includes a value""" 1554 if to_angle < from_angle: 1555 to_angle = math.pi * 2 + to_angle 1556 if angle < from_angle: 1557 angle = math.pi * 2 + angle 1558 1559 if angle > from_angle and angle < to_angle: 1560 return True 1561 return False 1562