vnet_data.py 47 KB

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