WME RA Util

Providing basic utility for RA adjustment without the need to delete & recreate

  1. // ==UserScript==
  2. // @name WME RA Util
  3. // @namespace https://greasyfork.org/users/30701-justins83-waze
  4. // @version 2025.03.3.01
  5. // @description Providing basic utility for RA adjustment without the need to delete & recreate
  6. // @include https://www.waze.com/editor*
  7. // @include https://www.waze.com/*/editor*
  8. // @include https://beta.waze.com/*
  9. // @exclude https://www.waze.com/user/editor*
  10. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  11. // @connect greasyfork.org
  12. // @author JustinS83
  13. // @grant GM_xmlhttpRequest
  14. // @license GPLv3
  15. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  16. // ==/UserScript==
  17.  
  18. /* global W */
  19. /* global WazeWrap */
  20. /* global OpenLayers */
  21. /* global require */
  22. /* global $ */
  23. /* global _ */
  24. /* global I18n */
  25. /* eslint curly: ["warn", "multi-or-nest"] */
  26.  
  27. /*
  28. non-normal RA color:#FF8000
  29. normal RA color:#4cc600
  30. */
  31. (function() {
  32.  
  33. var RAUtilWindow = null;
  34. var UpdateSegmentGeometry;
  35. var MoveNode, MultiAction;
  36. var drc_layer;
  37. let wEvents;
  38. const SCRIPT_VERSION = GM_info.script.version.toString();
  39. const SCRIPT_NAME = GM_info.script.name;
  40. const DOWNLOAD_URL = GM_info.scriptUpdateURL;
  41.  
  42. //var totalActions = 0;
  43. var _settings;
  44. const updateMessage = "Removed debugger lines. No more waa waas";
  45.  
  46. function bootstrap(tries = 1) {
  47.  
  48. if (W && W.map && W.model && require && WazeWrap.Ready){
  49. loadScriptUpdateMonitor();
  50. init();
  51. }
  52. else if (tries < 1000)
  53. setTimeout(function () {bootstrap(++tries);}, 200);
  54. }
  55.  
  56. bootstrap();
  57.  
  58. function loadScriptUpdateMonitor() {
  59. let updateMonitor;
  60. try {
  61. updateMonitor = new WazeWrap.Alerts.ScriptUpdateMonitor(SCRIPT_NAME, SCRIPT_VERSION, DOWNLOAD_URL, GM_xmlhttpRequest);
  62. updateMonitor.start();
  63. } catch (ex) {
  64. // Report, but don't stop if ScriptUpdateMonitor fails.
  65. console.error(`${SCRIPT_NAME}:`, ex);
  66. }
  67. }
  68.  
  69. function init(){
  70. injectCss();
  71. UpdateSegmentGeometry = require('Waze/Action/UpdateSegmentGeometry');
  72. MoveNode = require("Waze/Action/MoveNode");
  73. MultiAction = require("Waze/Action/MultiAction");
  74.  
  75. console.log("RA UTIL");
  76. console.log(GM_info.script);
  77. if(W.map.events)
  78. wEvents = W.map.events;
  79. else
  80. wEvents = W.map.getMapEventsListener();
  81.  
  82. RAUtilWindow = document.createElement('div');
  83. RAUtilWindow.id = "RAUtilWindow";
  84. RAUtilWindow.style.position = 'fixed';
  85. RAUtilWindow.style.visibility = 'hidden';
  86. RAUtilWindow.style.top = '15%';
  87. RAUtilWindow.style.left = '25%';
  88. RAUtilWindow.style.width = '510px';
  89. RAUtilWindow.style.zIndex = 100;
  90. RAUtilWindow.style.backgroundColor = '#FFFFFE';
  91. RAUtilWindow.style.borderWidth = '0px';
  92. RAUtilWindow.style.borderStyle = 'solid';
  93. RAUtilWindow.style.borderRadius = '10px';
  94. RAUtilWindow.style.boxShadow = '5px 5px 10px Silver';
  95. RAUtilWindow.style.padding = '4px';
  96.  
  97. var alertsHTML = '<div id="header" style="padding: 4px; background-color:#92C3D3; border-radius: 5px;-moz-border-radius: 5px;-webkit-border-radius: 5px; color: white; font-weight: bold; text-align:center; letter-spacing: 1px;text-shadow: black 0.1em 0.1em 0.2em;"><img src="https://storage.googleapis.com/wazeopedia-files/1/1e/RA_Util.png" style="float:left"></img> Roundabout Utility <a data-toggle="collapse" href="#divWrappers" id="collapserLink" style="float:right"><span id="collapser" style="cursor:pointer;padding:2px;color:white;" class="fa fa-caret-square-o-up"></a></span></div>';
  98. // start collapse // I put it al the beginning
  99. alertsHTML += '<div id="divWrappers" class="collapse in">';
  100. //***************** Round About Angles **************************
  101. alertsHTML += '<p style="margin: 10px 0px 0px 20px;"><input type="checkbox" id="chkRARoundaboutAngles">&nbsp;Enable Roundabout Angles</p>';
  102. //***************** Shift Amount **************************
  103. // Define BOX
  104. alertsHTML += '<div id="contentShift" style="text-align:center;float:left; width: 120px;max-width: 24%;height: 170px;margin: 1em 5px 0px 0px;opacity:1;border-radius: 2px;-moz-border-radius: 2px;-webkit-border-radius: 4px;border-width:1px;border-style:solid;border-color:#92C3D3;padding:2px;}">';
  105. alertsHTML += '<b>Shift amount</b></br><input type="text" name="shiftAmount" id="shiftAmount" size="1" style="float: left; text-align: center;font: inherit; line-height: normal; width: 30px; height: 20px; margin: 5px 4px; box-sizing: border-box; display: block; padding-left: 0; border-bottom-color: rgba(black,.3); background: transparent; outline: none; color: black;" value="1"/> <div style="margin: 5px 4px;">Meter(s)';
  106. // Shift amount controls
  107. alertsHTML += '<div id="controls" style="text-align:center; padding:06px 4px;width=100px; height=100px;margin: 5px 0px;border-style:solid; border-width: 2px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;box-shadow: inset 0px 0px 50px -14px rgba(0,0,0,1);-moz-box-shadow: inset 0px 0px 50px -14px rgba(0,0,0,1);-webkit-box-shadow: inset 0px 0px 50px -14px rgba(0,0,0,1); background:#92C3D3;align:center;">';
  108. //Single Shift Up Button
  109. alertsHTML += '<span id="RAShiftUpBtn" style="cursor:pointer;font-size:14px;">';
  110. alertsHTML += '<i class="fa fa-angle-double-up fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; vertical-align: top;"> </i>';
  111. alertsHTML += '<span id="UpBtnCaption" style="font-weight: bold;"></span>';
  112. alertsHTML += '</span><br>';
  113. //Single Shift Left Button
  114. alertsHTML += '<span id="RAShiftLeftBtn" style="cursor:pointer;font-size:14px;margin-left:-40px;">';
  115. alertsHTML += '<i class="fa fa-angle-double-left fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; vertical-align: middle"> </i>';
  116. alertsHTML += '<span id="LeftBtnCaption" style="font-weight: bold;"></span>';
  117. alertsHTML += '</span>';
  118. //Single Shift Right Button
  119. alertsHTML += '<span id="RAShiftRightBtn" style="float: right;cursor:pointer;font-size:14px;margin-right:5px;">';
  120. alertsHTML += '<i class="fa fa-angle-double-right fa-2x" style="color: white;text-shadow: black 0.1em 0.1em 0.2em; vertical-align: middle"> </i>';
  121. alertsHTML += '<span id="RightBtnCaption" style="font-weight: bold;"></span>';
  122. alertsHTML += '</span><br>';
  123. //Single Shift Down Button
  124. alertsHTML += '<span id="RAShiftDownBtn" style="cursor:pointer;font-size:14px;margin-top:0px;">';
  125. alertsHTML += '<i class="fa fa-angle-double-down fa-2x" style="color: white;text-shadow: black 0.1em 0.1em 0.2em; vertical-align: middle"> </i>';
  126. alertsHTML += '<span id="DownBtnCaption" style="font-weight: bold;"></span>';
  127. alertsHTML += '</span>';
  128. alertsHTML += '</div></div></div>';
  129. //***************** Rotation **************************
  130. // Define BOX
  131. alertsHTML += '<div id="contentRotate" style="float:left; text-align: center;width: 120px;max-width: 24%;max-height:145px;margin: 1em auto;opacity:1;border-radius: 2px;-moz-border-radius: 2px;-webkit-border-radius: 4px;border-width:1px;border-style:solid;border-color:#92C3D3;padding:2px; display:inline-block; border-style:solid; border-width:1px; height:152px; margin-right:5px;">';
  132. alertsHTML += '<b>Rotation amount</b></br><input type="text" name="rotationAmount" id="rotationAmount" size="1" style="float: left; text-align: center;font: inherit; line-height: normal; width: 30px; height: 20px; margin: 5px 4px; box-sizing: border-box; display: block; padding-left: 0; border-bottom-color: rgba(black,.3); background: transparent; outline: none; color: black;" value="1"/> <div style="margin: 5px 4px;">Degree(s)';
  133. // Rotation controls
  134. alertsHTML += '<div id="rotationControls" style="padding: 6px 4px;width=100px; margin: 20px 0px 50px 0px;align:center;">';
  135. // Rotate Button on the Left
  136. alertsHTML += '<span id="RARotateLeftBtn" class="btnRotate" style="float: left;">';
  137. alertsHTML += '<i class="fa fa-undo fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; padding:2px;"> </i>';
  138. alertsHTML += '<span id="RotateLeftBtnCaption" style="font-weight: bold;"></span>';
  139. alertsHTML += '</span>';
  140. // Rotate button on the Right
  141. alertsHTML += '<span id="RARotateRightBtn" class="btnRotate" style="float: right;">';
  142. alertsHTML += '<i class="fa fa-repeat fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; padding:2px;"> </i>';
  143. alertsHTML += '<span id="RotateRightBtnCaption" style="font-weight: bold;"></span>';
  144. alertsHTML += '</div></div></div>';
  145. //********************* Diameter change ******************
  146. // Define BOX
  147. alertsHTML += '<div id="diameterChange" style="float:left; text-align: center;width: 120px;max-width: 24%;max-height:145px;margin: 1em auto;opacity:1;border-radius: 2px;-moz-border-radius: 2px;-webkit-border-radius: 4px;border-width:1px;border-style:solid;border-color:#92C3D3;padding:2px; display:inline-block; border-style:solid; border-width:1px; height:152px; margin-right:5px;">';
  148. alertsHTML += '<b>Change diameter</b></br></br>';
  149. // Diameter Change controls
  150. alertsHTML += '<div id="DiameterChangeControls" style="padding: 6px 4px;width=100px; margin: 5px 7px 50px 7px;align:center;">';
  151. // Decrease Button
  152. alertsHTML += '<span id="diameterChangeDecreaseBtn" style="float: left; width=45px; height=45px; background-color:#92C3D3; cursor:pointer; padding: 5px; font-size:14px; border:thin outset black; border-style:solid; border-width: 1px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-moz-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-webkit-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);">';
  153. alertsHTML += '<i class="fa fa-compress fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; padding:2px;;"> </i>';
  154. alertsHTML += '<span id="diameterChangeDecreaseCaption" style="font-weight: bold;"></span>';
  155. alertsHTML += '</span>';
  156. // Increase Button
  157. alertsHTML += '<span id="diameterChangeIncreaseBtn" style="float: right; width=45px; height=45px; background-color:#92C3D3; cursor:pointer; padding: 5px; font-size:14px; border:thin outset black; border-style:solid; border-width: 1px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-moz-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-webkit-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);">';
  158. alertsHTML += '<i class="fa fa-arrows-alt fa-2x" style="color: white; text-shadow: black 0.1em 0.1em 0.2em; padding:2px;"> </i>';
  159. alertsHTML += '<span id="diameterChangeIncreaseCaption" style="font-weight: bold;"></span>';
  160. alertsHTML += '</span>';
  161. alertsHTML += '</div></div>';
  162. //***************** Bump nodes **********************
  163. // Define BOX
  164. alertsHTML += '<div id="bumpNodes" style="float:left; text-align: center;width: 120px;max-width: 24%;max-height:145px;margin: 1em auto 0px auto;opacity:1;border-radius: 2px;-moz-border-radius: 2px;-webkit-border-radius: 4px;border-width:1px;border-style:solid;border-color:#92C3D3;padding:2px; display:inline-block; border-style:solid; border-width:1px; height:152px; margin-right:5px;">';
  165. alertsHTML += '<b>Move nodes</b></br>';
  166. // Move Nodes controls
  167. alertsHTML += '<div id="MoveNodesControls" style="padding: 2px;">';
  168. // Button A
  169. alertsHTML += '<div style="text-align:center; font-size:18px;">A Node';
  170. // Move node IN
  171. alertsHTML += '<p><span id="btnMoveANodeIn" class="btnMoveNode" style="color: white; font-size: 0.875em; text-shadow: black 0.1em 0.1em 0.2em; padding:3px 15px 3px 15px; margin:3px;">in</span>';
  172. // Move node OUT
  173. alertsHTML += '<span id="btnMoveANodeOut" class="btnMoveNode" class="btnMoveNode" style="color: white; font-size: 0.875em; text-shadow: black 0.1em 0.1em 0.2em; padding:3px 10px 3px 10px; margin:3px;">out</span>';
  174. alertsHTML += '</div>';
  175. // Button B
  176. alertsHTML += '<div style="text-align:center; font-size:18px;">B Node';
  177. // Move node IN
  178. alertsHTML += '<p><span id="btnMoveBNodeIn" class="btnMoveNode" style="color: white; font-size: 0.875em; text-shadow: black 0.1em 0.1em 0.2em; padding:3px 15px 3px 15px; margin:3px;">in</span>';
  179. // Move node OUT
  180. alertsHTML += '<span id="btnMoveBNodeOut" class="btnMoveNode" class="btnMoveNode" style="color: white; font-size: 0.875em; text-shadow: black 0.1em 0.1em 0.2em; padding:3px 10px 3px 10px; margin:3px;">out</span>';
  181. alertsHTML += '</div>';
  182. alertsHTML += '</div></div></div>';
  183.  
  184.  
  185. RAUtilWindow.innerHTML = alertsHTML;
  186. document.body.appendChild(RAUtilWindow);
  187.  
  188. $('#RAShiftLeftBtn').click(RAShiftLeftBtnClick);
  189. $('#RAShiftRightBtn').click(RAShiftRightBtnClick);
  190. $('#RAShiftUpBtn').click(RAShiftUpBtnClick);
  191. $('#RAShiftDownBtn').click(RAShiftDownBtnClick);
  192.  
  193. $('#RARotateLeftBtn').click(RARotateLeftBtnClick);
  194. $('#RARotateRightBtn').click(RARotateRightBtnClick);
  195.  
  196. $('#diameterChangeDecreaseBtn').click(diameterChangeDecreaseBtnClick);
  197. $('#diameterChangeIncreaseBtn').click(diameterChangeIncreaseBtnClick);
  198.  
  199. $('#btnMoveANodeIn').click(function(){moveNodeIn(WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.id, WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.fromNodeID);});
  200. $('#btnMoveANodeOut').click(function(){moveNodeOut(WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.id, WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.fromNodeID);});
  201. $('#btnMoveBNodeIn').click(function(){moveNodeIn(WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.id, WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.toNodeID);});
  202. $('#btnMoveBNodeOut').click(function(){moveNodeOut(WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.id, WazeWrap.getSelectedFeatures()[0].WW.getObjectModel().attributes.toNodeID);});
  203.  
  204. $('#shiftAmount').keypress(function(event) {
  205. if ((event.which != 46 || $(this).val().indexOf('.') != -1) && (event.which < 48 || event.which > 57))
  206. event.preventDefault();
  207. });
  208.  
  209. $('#rotationAmount').keypress(function(event) {
  210. if ((event.which != 46 || $(this).val().indexOf('.') != -1) && (event.which < 48 || event.which > 57))
  211. event.preventDefault();
  212. });
  213.  
  214. $('#collapserLink').click(function(){
  215. $("#divWrappers").slideToggle("fast");
  216. if($('#collapser').attr('class') == "fa fa-caret-square-o-down"){
  217. $("#collapser").removeClass("fa-caret-square-o-down");
  218. $("#collapser").addClass("fa-caret-square-o-up");
  219. }
  220. else{
  221. $("#collapser").removeClass("fa-caret-square-o-up");
  222. $("#collapser").addClass("fa-caret-square-o-down");
  223. }
  224. saveSettingsToStorage();
  225. });
  226.  
  227. W.selectionManager.events.register("selectionchanged", null, checkDisplayTool);
  228. //W.model.actionManager.events.register("afterundoaction",null, undotriggered);
  229. //W.model.actionManager.events.register("afterclearactions",null,actionsCleared);
  230.  
  231. var loadedSettings = $.parseJSON(localStorage.getItem("WME_RAUtil"));
  232. var defaultSettings = {
  233. divTop: "15%",
  234. divLeft: "25%",
  235. Expanded: true,
  236. RoundaboutAngles: true
  237. };
  238. _settings = loadedSettings ? loadedSettings : defaultSettings;
  239.  
  240. $('#RAUtilWindow').css('left', _settings.divLeft);
  241. $('#RAUtilWindow').css('top', _settings.divTop);
  242. $("#chkRARoundaboutAngles").prop('checked', _settings.RoundaboutAngles);
  243. $("#chkRARoundaboutAngles").prop('checked', _settings.RoundaboutAngles);
  244.  
  245. if(!_settings.Expanded){
  246. //$("#divWrappers").removeClass("in");
  247. //$("#divWrappers").addClass("collapse");
  248. $("#divWrappers").hide();
  249. $("#collapser").removeClass("fa-caret-square-o-up");
  250. $("#collapser").addClass("fa-caret-square-o-down");
  251. }
  252.  
  253. $("#chkRARoundaboutAngles").click(function(){
  254. saveSettingsToStorage();
  255.  
  256. if($("#chkRARoundaboutAngles").is(":checked")){
  257. wEvents.register("zoomend", null, DrawRoundaboutAngles);
  258. wEvents.register("moveend", null, DrawRoundaboutAngles);
  259. DrawRoundaboutAngles();
  260. drc_layer.setVisibility(true);
  261. }
  262. else{
  263. wEvents.unregister("zoomend", null, DrawRoundaboutAngles);
  264. wEvents.unregister("moveend", null, DrawRoundaboutAngles);
  265. drc_layer.setVisibility(false);
  266. }
  267. });
  268.  
  269. if(_settings.RoundaboutAngles){
  270. wEvents.register("zoomend", null, DrawRoundaboutAngles);
  271. wEvents.register("moveend", null, DrawRoundaboutAngles);
  272. DrawRoundaboutAngles();
  273. }
  274.  
  275. WazeWrap.Interface.ShowScriptUpdate("WME RA Util", GM_info.script.version, updateMessage, "https://greasyfork.org/en/scripts/23616-wme-ra-util", "https://www.waze.com/forum/viewtopic.php?f=819&t=211079");
  276. }
  277.  
  278. function saveSettingsToStorage() {
  279. if (localStorage) {
  280. var settings = {
  281. divTop: "15%",
  282. divLeft: "25%",
  283. Expanded: true,
  284. RoundaboutAngles: true
  285. };
  286.  
  287. settings.divLeft = $('#RAUtilWindow').css('left');
  288. settings.divTop = $('#RAUtilWindow').css('top');
  289. settings.Expanded = $("#collapser").attr('class').indexOf("fa-caret-square-o-up") > -1;
  290. settings.RoundaboutAngles = $("#chkRARoundaboutAngles").is(":checked");
  291. localStorage.setItem("WME_RAUtil", JSON.stringify(settings));
  292. }
  293. }
  294.  
  295. function checkDisplayTool(){
  296. if(WazeWrap.hasSelectedFeatures() && WazeWrap.getSelectedFeatures()[0].WW.getType() === 'segment'){
  297. if(!AllSelectedSegmentsRA() || WazeWrap.getSelectedFeatures().length === 0)
  298. $('#RAUtilWindow').css({'visibility': 'hidden'});
  299. else{
  300. $('#RAUtilWindow').css({'visibility': 'visible'});
  301. if(typeof jQuery.ui !== 'undefined')
  302. $('#RAUtilWindow' ).draggable({ //Gotta nuke the height setting the dragging inserts otherwise the panel cannot collapse
  303. stop: function(event, ui) {
  304. $('#RAUtilWindow').css("height", "");
  305. saveSettingsToStorage();
  306. }
  307. });
  308. //checkSaveChanges();
  309. checkAllEditable(WazeWrap.Model.getAllRoundaboutSegmentsFromObj(WazeWrap.getSelectedFeatures()[0]));
  310. }
  311. }
  312. else{
  313. $('#RAUtilWindow').css({'visibility': 'hidden'});
  314. if(typeof jQuery.ui !== 'undefined')
  315. $('#RAUtilWindow' ).draggable({
  316. stop: function(event, ui) {
  317. $('#RAUtilWindow').css("height", "");
  318. saveSettingsToStorage();
  319. }
  320. });
  321. }
  322. }
  323.  
  324. function checkAllEditable(RASegs){
  325. var $RAEditable = $('#RAEditable');
  326. var allEditable = true;
  327. var segObj, fromNode, toNode;
  328.  
  329. for(let i=0; i<RASegs.length;i++){
  330. segObj = W.model.segments.getObjectById(RASegs[i]);
  331. fromNode = segObj.getFromNode();
  332. toNode = segObj.getToNode();
  333.  
  334. if(segObj !== "undefined"){
  335. if(fromNode && fromNode !== "undefined" && !fromNode.areConnectionsEditable())
  336. allEditable = false;
  337. else if(toNode && toNode !== "undefined" && !toNode.areConnectionsEditable())
  338. allEditable = false;
  339. var toConnected, fromConnected;
  340.  
  341. if(toNode){
  342. toConnected = toNode.attributes.segIDs;
  343. for(let j=0;j<toConnected.length;j++){
  344. if(W.model.segments.getObjectById(toConnected[j]) !== "undefined")
  345. if(W.model.segments.getObjectById(toConnected[j]).hasClosures())
  346. allEditable = false;
  347. }
  348. }
  349.  
  350. if(fromNode){
  351. fromConnected = fromNode.attributes.segIDs;
  352. for(let j=0;j<fromConnected.length;j++){
  353. if(W.model.segments.getObjectById(fromConnected[j]) !== "undefined")
  354. if(W.model.segments.getObjectById(fromConnected[j]).hasClosures())
  355. allEditable = false;
  356. }
  357. }
  358. }
  359. }
  360. if(allEditable)
  361. $RAEditable.remove();
  362. else{
  363. if($RAEditable.length === 0){
  364. $RAEditable = $('<div>', {id:'RAEditable', style:'color:red'});
  365. $RAEditable.text('One or more segments are locked above your rank or have a closure.');
  366. $('#RAUtilWindow').append($RAEditable);
  367. }
  368. }
  369. return allEditable;
  370. }
  371.  
  372. function AllSelectedSegmentsRA(){
  373. for (let i = 0; i < WazeWrap.getSelectedFeatures().length; i++){
  374. if(WazeWrap.getSelectedFeatures()[i].WW.getObjectModel().attributes.id < 0 || !WazeWrap.Model.isRoundaboutSegmentID(WazeWrap.getSelectedFeatures()[i].WW.getObjectModel().attributes.id))
  375. return false;
  376. }
  377. return true;
  378. }
  379.  
  380. function ShiftSegmentNodesLat(segObj, latOffset){
  381. var RASegs = WazeWrap.Model.getAllRoundaboutSegmentsFromObj(segObj);
  382. if(checkAllEditable(RASegs)){
  383. var newGeometry, originalLength;
  384. var multiaction = new MultiAction();
  385. // multiaction.setModel(W.model);
  386.  
  387. for(let i=0; i<RASegs.length; i++){
  388. segObj = W.model.segments.getObjectById(RASegs[i]);
  389. newGeometry = structuredClone(segObj.attributes.geoJSONGeometry);
  390. originalLength = segObj.attributes.geoJSONGeometry.coordinates.length;
  391. for(j=1; j < originalLength-1; j++){
  392. newGeometry.coordinates[j][1] += latOffset;
  393. }
  394. //W.model.actionManager.add(new UpdateSegmentGeometry(segObj, segObj.geometry, newGeometry));
  395. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(segObj, segObj.attributes.geoJSONGeometry, newGeometry));
  396.  
  397. var node = W.model.nodes.objects[segObj.attributes.toNodeID];
  398. if(segObj.attributes.revDirection)
  399. node = W.model.nodes.objects[segObj.attributes.fromNodeID];
  400. var newNodeGeometry = structuredClone(node.attributes.geoJSONGeometry);
  401. newNodeGeometry.coordinates[1] += latOffset;
  402.  
  403. var connectedSegObjs = {};
  404. var emptyObj = {};
  405. for(var j=0;j<node.attributes.segIDs.length;j++){
  406. var segid = node.attributes.segIDs[j];
  407. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  408. }
  409. //W.model.actionManager.add(new MoveNode(segObj, segObj.geometry, newNodeGeometry, connectedSegObjs, i));
  410. multiaction.doSubAction(W.model, new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  411. //W.model.actionManager.add(new MoveNode(node, node.geometry, newNodeGeometry));
  412. //totalActions +=2;
  413. }
  414. W.model.actionManager.add(multiaction);
  415. }
  416. }
  417.  
  418. function ShiftSegmentsNodesLong(segObj, longOffset){
  419. var RASegs = WazeWrap.Model.getAllRoundaboutSegmentsFromObj(segObj);
  420. if(checkAllEditable(RASegs)){
  421. var newGeometry, originalLength;
  422. var multiaction = new MultiAction();
  423. // multiaction.setModel(W.model);
  424.  
  425. //Loop through all RA segments & adjust
  426. for(let i=0; i<RASegs.length; i++){
  427. segObj = W.model.segments.getObjectById(RASegs[i]);
  428. newGeometry = structuredClone(segObj.attributes.geoJSONGeometry);
  429. originalLength = segObj.attributes.geoJSONGeometry.coordinates.length;
  430. for(let j=1; j < originalLength-1; j++){
  431. newGeometry.coordinates[j][0] += longOffset;
  432. }
  433. //W.model.actionManager.add(new UpdateSegmentGeometry(segObj, segObj.geometry, newGeometry));
  434. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(segObj, segObj.attributes.geoJSONGeometry, newGeometry));
  435.  
  436. var node = W.model.nodes.objects[segObj.attributes.toNodeID];
  437. if(segObj.attributes.revDirection)
  438. node = W.model.nodes.objects[segObj.attributes.fromNodeID];
  439.  
  440. var newNodeGeometry = structuredClone(node.attributes.geoJSONGeometry);
  441. newNodeGeometry.coordinates[0] += longOffset;
  442.  
  443. var connectedSegObjs = {};
  444. var emptyObj = {};
  445. for(let j=0;j<node.attributes.segIDs.length;j++){
  446. var segid = node.attributes.segIDs[j];
  447. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  448. }
  449. //W.model.actionManager.add(new MoveNode(node, node.geometry, newNodeGeometry));
  450. multiaction.doSubAction(W.model, new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  451. //totalActions +=2;
  452. }
  453. W.model.actionManager.add(multiaction);
  454. }
  455. }
  456.  
  457. function rotatePoints(origin, points, angle){
  458. var lineFeature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(points),null,null);
  459. lineFeature.geometry.rotate(angle, new OpenLayers.Geometry.Point(origin[0], origin[1]));
  460. return [].concat(lineFeature.geometry.components);
  461. }
  462.  
  463. function RotateRA(segObj, angle){
  464. var RASegs = WazeWrap.Model.getAllRoundaboutSegmentsFromObj(segObj);
  465. var raCenter = W.model.junctions.objects[segObj.WW.getAttributes().junctionID].attributes.geoJSONGeometry.coordinates;
  466.  
  467. if(checkAllEditable(RASegs)){
  468. var gps, newGeometry, originalLength;
  469. var multiaction = new MultiAction();
  470. // multiaction.setModel(W.model);
  471.  
  472. //Loop through all RA segments & adjust
  473. for(let i=0; i<RASegs.length; i++){
  474. segObj = W.model.segments.getObjectById(RASegs[i]);
  475. newGeometry = structuredClone(segObj.attributes.geoJSONGeometry);
  476. originalLength = segObj.attributes.geoJSONGeometry.coordinates.length;
  477.  
  478. var center = raCenter; //WazeWrap.Geometry.ConvertTo900913(raCenter.x, raCenter.y);
  479. var segPoints = [];
  480. //Have to copy the points manually (can't use .clone()) otherwise the geometry rotation modifies the geometry of the segment itself and that hoses WME.
  481. for(let j=0; j<originalLength;j++)
  482. segPoints.push(new OpenLayers.Geometry.Point(segObj.attributes.geoJSONGeometry.coordinates[j][0], segObj.attributes.geoJSONGeometry.coordinates[j][1]));
  483.  
  484. var newPoints = rotatePoints(center, segPoints, angle);
  485.  
  486. for(let j=1; j<originalLength-1;j++){
  487. newGeometry.coordinates[j] = [newPoints[j].x, newPoints[j].y];
  488. }
  489.  
  490. //W.model.actionManager.add(new UpdateSegmentGeometry(segObj, segObj.geometry, newGeometry));
  491. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(segObj, segObj.attributes.geoJSONGeometry, newGeometry));
  492.  
  493. //**************Rotate Nodes******************
  494. var node = W.model.nodes.objects[segObj.attributes.toNodeID];
  495. if(segObj.attributes.revDirection)
  496. node = W.model.nodes.objects[segObj.attributes.fromNodeID];
  497.  
  498. var nodePoints = [];
  499. var newNodeGeometry = structuredClone(node.attributes.geoJSONGeometry);
  500.  
  501. nodePoints.push(new OpenLayers.Geometry.Point(node.attributes.geoJSONGeometry.coordinates[0], node.attributes.geoJSONGeometry.coordinates[1]));
  502. nodePoints.push(new OpenLayers.Geometry.Point(node.attributes.geoJSONGeometry.coordinates[0], node.attributes.geoJSONGeometry.coordinates[1])); //add it twice because lines need 2 points
  503.  
  504. gps = rotatePoints(center, nodePoints, angle);
  505.  
  506. newNodeGeometry.coordinates = [gps[0].x, gps[0].y];
  507.  
  508. var connectedSegObjs = {};
  509. var emptyObj = {};
  510. for(let j=0;j<node.attributes.segIDs.length;j++){
  511. var segid = node.attributes.segIDs[j];
  512. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  513. }
  514. multiaction.doSubAction(W.model, new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  515. //totalActions +=2;
  516. }
  517. W.model.actionManager.add(multiaction);
  518. }
  519. }
  520.  
  521. function RARotateLeftBtnClick(e){
  522. e.stopPropagation();
  523. var segObj = WazeWrap.getSelectedFeatures()[0];
  524. RotateRA(segObj, $('#rotationAmount').val());
  525. }
  526.  
  527. function RARotateRightBtnClick(e){
  528. e.stopPropagation();
  529.  
  530. var segObj = WazeWrap.getSelectedFeatures()[0];
  531. RotateRA(segObj, -$('#rotationAmount').val());
  532. }
  533.  
  534. function ChangeDiameter(segObj, amount){
  535. var RASegs = WazeWrap.Model.getAllRoundaboutSegmentsFromObj(segObj);
  536. var raCenter = W.model.junctions.objects[segObj.WW.getAttributes().junctionID].attributes.geoJSONGeometry.coordinates;
  537. let { lon: centerX, lat: centerY } = WazeWrap.Geometry.ConvertTo900913(raCenter);
  538.  
  539. if(checkAllEditable(RASegs)){
  540. var newGeometry, originalLength;
  541.  
  542. //Loop through all RA segments & adjust
  543. for(let i=0; i<RASegs.length; i++){
  544. segObj = W.model.segments.getObjectById(RASegs[i]);
  545. newGeometry = structuredClone(segObj.attributes.geoJSONGeometry);
  546. originalLength = segObj.attributes.geoJSONGeometry.coordinates.length;
  547.  
  548. for(let j=1; j < originalLength-1; j++){
  549. let pt = segObj.attributes.geoJSONGeometry.coordinates[j];
  550. let { lon: pointX, lat: pointY } = WazeWrap.Geometry.ConvertTo900913(pt);
  551. let h = Math.sqrt(Math.abs(Math.pow(pointX - centerX, 2) + Math.pow(pointY - centerY, 2)));
  552. let ratio = (h + amount)/h;
  553. let x = centerX + (pointX - centerX) * ratio;
  554. let y = centerY + (pointY - centerY) * ratio;
  555.  
  556. let { lon: newX, lat: newY } = WazeWrap.Geometry.ConvertTo4326([x, y]);
  557. newGeometry.coordinates[j] = [newX, newY];
  558. }
  559. W.model.actionManager.add(new UpdateSegmentGeometry(segObj, segObj.attributes.geoJSONGeometry, newGeometry));
  560.  
  561. var node = W.model.nodes.objects[segObj.attributes.toNodeID];
  562. if(segObj.attributes.revDirection)
  563. node = W.model.nodes.objects[segObj.attributes.fromNodeID];
  564.  
  565. var newNodeGeometry = structuredClone(node.attributes.geoJSONGeometry);
  566. let { lon: pointX, lat: pointY } = WazeWrap.Geometry.ConvertTo900913(newNodeGeometry.coordinates);
  567. let h = Math.sqrt(Math.abs(Math.pow(pointX - centerX, 2) + Math.pow(pointY - centerY, 2)));
  568. let ratio = (h + amount)/h;
  569. let x = centerX + (pointX - centerX) * ratio;
  570. let y = centerY + (pointY - centerY) * ratio;
  571.  
  572. let { lon: newX, lat: newY } = WazeWrap.Geometry.ConvertTo4326([x, y]);
  573. newNodeGeometry.coordinates = [newX, newY];
  574.  
  575. var connectedSegObjs = {};
  576. var emptyObj = {};
  577. for(let j=0;j<node.attributes.segIDs.length;j++){
  578. var segid = node.attributes.segIDs[j];
  579. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  580. }
  581. W.model.actionManager.add(new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  582. }
  583. if(_settings.RoundaboutAngles)
  584. DrawRoundaboutAngles();
  585. }
  586. }
  587.  
  588. function diameterChangeDecreaseBtnClick(e){
  589. e.stopPropagation();
  590. var segObj = WazeWrap.getSelectedFeatures()[0];
  591. ChangeDiameter(segObj, -1);
  592. }
  593.  
  594. function diameterChangeIncreaseBtnClick(e){
  595. e.stopPropagation();
  596. var segObj = WazeWrap.getSelectedFeatures()[0];
  597. ChangeDiameter(segObj, 1);
  598. }
  599.  
  600. function moveNodeIn(sourceSegID, nodeID){
  601. let isANode = true;
  602. let curSeg = W.model.segments.getObjectById(sourceSegID);
  603. if (curSeg.attributes.geoJSONGeometry.coordinates.length > 2) {
  604. if(nodeID === curSeg.attributes.toNodeID)
  605. isANode = false;
  606. //Add geo point on the other segment
  607. let node = W.model.nodes.getObjectById(nodeID);
  608. let currNodePOS = structuredClone(node.attributes.geoJSONGeometry.coordinates);
  609. let otherSeg; //other RA segment that we are adding a geo point to
  610. let nodeSegs = [...W.model.nodes.getObjectById(nodeID).attributes.segIDs];
  611. nodeSegs = _.without(nodeSegs, sourceSegID); //remove the source segment from the node Segs - we need to find the segment that is a part of the RA that is prior to our source seg
  612. for(let i=0; i<nodeSegs.length; i++){
  613. let s = W.model.segments.getObjectById(nodeSegs[i]);
  614. if(s.attributes.junctionID){
  615. otherSeg = s;
  616. break;
  617. }
  618. }
  619.  
  620. var multiaction = new MultiAction();
  621. // multiaction.setModel(W.model);
  622. //note and remove first geo point, move junction node to this point
  623. var newNodeGeometry = { type: 'Point', coordinates: structuredClone(curSeg.attributes.geoJSONGeometry.coordinates[isANode ? 1 : curSeg.attributes.geoJSONGeometry.coordinates.length - 2]) };
  624.  
  625. let newSegGeo = structuredClone(curSeg.attributes.geoJSONGeometry);
  626. newSegGeo.coordinates.splice(isANode ? 1 : newSegGeo.coordinates.length - 2, 1);
  627. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(curSeg, curSeg.attributes.geoJSONGeometry, newSegGeo));
  628.  
  629. //move the node
  630. var connectedSegObjs = {};
  631. var emptyObj = {};
  632. for(var j=0;j<node.attributes.segIDs.length;j++){
  633. var segid = node.attributes.segIDs[j];
  634. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  635. }
  636. multiaction.doSubAction(W.model, new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  637.  
  638. if((otherSeg.attributes.revDirection && !curSeg.attributes.revDirection) || (!otherSeg.attributes.revDirection && curSeg.attributes.revDirection))
  639. isANode = !isANode;
  640.  
  641. let newGeo = structuredClone(otherSeg.attributes.geoJSONGeometry);
  642. newGeo.coordinates.splice(isANode ? -1 : 1, 0, [currNodePOS[0], currNodePOS[1]]);
  643. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(otherSeg, otherSeg.attributes.geoJSONGeometry, newGeo));
  644. W.model.actionManager.add(multiaction);
  645.  
  646. if(_settings.RoundaboutAngles)
  647. DrawRoundaboutAngles();
  648. }
  649. }
  650.  
  651. function moveNodeOut(sourceSegID, nodeID){
  652. let isANode = true;
  653. let curSeg = W.model.segments.getObjectById(sourceSegID);
  654. if(nodeID === curSeg.attributes.toNodeID)
  655. isANode = false;
  656. //Add geo point on the other segment
  657. let node = W.model.nodes.getObjectById(nodeID);
  658. let currNodePOS = structuredClone(node.attributes.geoJSONGeometry.coordinates);
  659. let otherSeg; //other RA segment that we are adding a geo point to
  660. let nodeSegs = [...W.model.nodes.getObjectById(nodeID).attributes.segIDs];
  661. nodeSegs = _.without(nodeSegs, sourceSegID); //remove the source segment from the node Segs - we need to find the segment that is a part of the RA that is after our source seg
  662. for(let i=0; i<nodeSegs.length; i++){
  663. let s = W.model.segments.getObjectById(nodeSegs[i]);
  664. if(s.attributes.junctionID){
  665. otherSeg = s;
  666. break;
  667. }
  668. }
  669.  
  670. if(otherSeg.attributes.geoJSONGeometry.coordinates.length > 2){
  671. let newSegGeo = structuredClone(curSeg.attributes.geoJSONGeometry);
  672. newSegGeo.coordinates.splice(isANode ? 1 : newSegGeo.coordinates.length - 1, 0, [currNodePOS[0], currNodePOS[1]]);
  673. var multiaction = new MultiAction();
  674. // multiaction.setModel(W.model);
  675. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(curSeg, curSeg.attributes.geoJSONGeometry, newSegGeo));
  676. if((otherSeg.attributes.revDirection && !curSeg.attributes.revDirection) || (!otherSeg.attributes.revDirection && curSeg.attributes.revDirection))
  677. isANode = !isANode;
  678.  
  679. //note and remove first geo point, move junction node to this point
  680. var newNodeGeometry = { type: 'Point', coordinates: structuredClone(otherSeg.attributes.geoJSONGeometry.coordinates[isANode ? otherSeg.attributes.geoJSONGeometry.coordinates.length - 2 : 1]) };
  681. let newGeo = structuredClone(otherSeg.attributes.geoJSONGeometry);
  682. newGeo.coordinates.splice(isANode ? -2 : 1, 1);
  683. multiaction.doSubAction(W.model, new UpdateSegmentGeometry(otherSeg, otherSeg.attributes.geoJSONGeometry, newGeo));
  684.  
  685. //move the node
  686. var connectedSegObjs = {};
  687. var emptyObj = {};
  688. for(var j=0; j < node.attributes.segIDs.length;j++){
  689. var segid = node.attributes.segIDs[j];
  690. connectedSegObjs[segid] = structuredClone(W.model.segments.getObjectById(segid).attributes.geoJSONGeometry);
  691. }
  692. multiaction.doSubAction(W.model, new MoveNode(node, node.attributes.geoJSONGeometry, newNodeGeometry, connectedSegObjs, emptyObj));
  693. W.model.actionManager.add(multiaction);
  694.  
  695. if(_settings.RoundaboutAngles)
  696. DrawRoundaboutAngles();
  697. }
  698. }
  699.  
  700. //Left
  701. function RAShiftLeftBtnClick(e){
  702. // this traps the click to prevent it falling through to the underlying area name element and potentially causing the map view to be relocated to that area...
  703. e.stopPropagation();
  704.  
  705. //if(!pendingChanges){
  706. var segObj = WazeWrap.getSelectedFeatures()[0];
  707. var convertedCoords = WazeWrap.Geometry.ConvertTo4326(segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][0], segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][1]);
  708. var gpsOffsetAmount = WazeWrap.Geometry.CalculateLongOffsetGPS(-$('#shiftAmount').val(), convertedCoords.lon, convertedCoords.lat);
  709. ShiftSegmentsNodesLong(segObj, gpsOffsetAmount);
  710. //}
  711. }
  712. //Right
  713. function RAShiftRightBtnClick(e){
  714. // this traps the click to prevent it falling through to the underlying area name element and potentially causing the map view to be relocated to that area...
  715. e.stopPropagation();
  716.  
  717. //if(!pendingChanges){
  718. var segObj = WazeWrap.getSelectedFeatures()[0];
  719. var convertedCoords = WazeWrap.Geometry.ConvertTo4326(segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][0], segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][1]);
  720. var gpsOffsetAmount = WazeWrap.Geometry.CalculateLongOffsetGPS($('#shiftAmount').val(), convertedCoords.lon, convertedCoords.lat);
  721. ShiftSegmentsNodesLong(segObj, gpsOffsetAmount);
  722. //}
  723. }
  724. //Up
  725. function RAShiftUpBtnClick(e){
  726. // this traps the click to prevent it falling through to the underlying area name element and potentially causing the map view to be relocated to that area...
  727. e.stopPropagation();
  728.  
  729. //if(!pendingChanges){
  730. var segObj = WazeWrap.getSelectedFeatures()[0];
  731. var gpsOffsetAmount = WazeWrap.Geometry.CalculateLatOffsetGPS($('#shiftAmount').val(), WazeWrap.Geometry.ConvertTo4326(segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][0], segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][1]));
  732. ShiftSegmentNodesLat(segObj, gpsOffsetAmount);
  733. //}
  734. }
  735. //Down
  736. function RAShiftDownBtnClick(e){
  737. // this traps the click to prevent it falling through to the underlying area name element and potentially causing the map view to be relocated to that area...
  738. e.stopPropagation();
  739.  
  740. //if(!pendingChanges){
  741. var segObj = WazeWrap.getSelectedFeatures()[0];
  742. var gpsOffsetAmount = WazeWrap.Geometry.CalculateLatOffsetGPS(-$('#shiftAmount').val(), WazeWrap.Geometry.ConvertTo4326(segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][0], segObj.WW.getAttributes().geoJSONGeometry.coordinates[0][1]));
  743. ShiftSegmentNodesLat(segObj, gpsOffsetAmount);
  744. //}
  745. }
  746.  
  747. //*************** Roundabout Angles **********************
  748. function DrawRoundaboutAngles(){
  749. //---------get or create layer
  750. var layers = W.map.getLayersBy("uniqueName","__DrawRoundaboutAngles");
  751.  
  752. if(layers.length > 0)
  753. drc_layer = layers[0];
  754. else {
  755. let drc_style = new OpenLayers.Style({
  756. fillOpacity: 0.0,
  757. strokeOpacity: 1.0,
  758. fillColor: "#FF40C0",
  759. strokeColor: "${strokeColor}",
  760. strokeWidth: 10,
  761. fontWeight: "bold",
  762. pointRadius: 0,
  763. label : "${labelText}",
  764. fontFamily: "Tahoma, Courier New",
  765. labelOutlineColor: "#FFFFFF",
  766. labelOutlineWidth: 3,
  767. fontColor: "${labelColor}",
  768. fontSize: "10px"
  769. });
  770.  
  771. drc_layer = new OpenLayers.Layer.Vector("Roundabout Angles", {
  772. displayInLayerSwitcher: true,
  773. uniqueName: "__DrawRoundaboutAngles",
  774. styleMap: new OpenLayers.StyleMap(drc_style)
  775. });
  776.  
  777. I18n.translations[I18n.currentLocale()].layers.name["__DrawRoundaboutAngles"] = "Roundabout Angles";
  778. W.map.addLayer(drc_layer);
  779.  
  780. drc_layer.setVisibility(true);
  781. }
  782.  
  783. localStorage.WMERAEnabled = drc_layer.visibility;
  784.  
  785. if (drc_layer.visibility == false) {
  786. drc_layer.removeAllFeatures();
  787. return;
  788. }
  789.  
  790. if (W.map.getZoom() < 1) {
  791. drc_layer.removeAllFeatures();
  792. return;
  793. }
  794.  
  795. //---------collect all roundabouts first
  796. var rsegments = {};
  797.  
  798. for (let iseg in W.model.segments.objects) {
  799. let isegment = W.model.segments.getObjectById(iseg);
  800. let iattributes = isegment.attributes;
  801. let iline = isegment.getOLGeometry().id;
  802.  
  803. let irid = iattributes.junctionID;
  804.  
  805. if (iline !== null && irid != undefined) {
  806. let rsegs = rsegments[irid];
  807. if (rsegs == undefined)
  808. rsegments[irid] = rsegs = new Array();
  809. rsegs.push(isegment);
  810. }
  811. }
  812.  
  813. var drc_features = [];
  814.  
  815. //-------for each roundabout do...
  816. for (let irid in rsegments) {
  817. let rsegs = rsegments[irid];
  818.  
  819. let isegment = rsegs[0];
  820.  
  821. let nodes = [];
  822. let nodes_x = [];
  823. let nodes_y = [];
  824.  
  825. nodes = rsegs.map(seg => seg.attributes.fromNodeID); //get from nodes
  826. nodes = [...nodes, ...rsegs.map(seg => seg.attributes.toNodeID)]; //get to nodes add to from nodes
  827. nodes = _.uniq(nodes); //remove duplicates
  828.  
  829. let node_objects = W.model.nodes.getByIds(nodes);
  830. nodes_x = node_objects.map(n => n.getOLGeometry().x); //get all x locations
  831. nodes_y = node_objects.map(n => n.getOLGeometry().y); //get all y locations
  832.  
  833. let sr_x = 0;
  834. let sr_y = 0;
  835. let radius = 0;
  836. let numNodes = nodes_x.length;
  837.  
  838. if (numNodes >= 1) {
  839. let ax = nodes_x[0];
  840. let ay = nodes_y[0];
  841. let junction = W.model.junctions.getObjectById(irid);
  842. //var junction_coords = junction && junction.getOLGeometry() && junction.getOLGeometry().coordinates;
  843.  
  844. // if (junction_coords && junction_coords.length == 2) {
  845. //---------- get center point from junction model
  846. //let lonlat = new OpenLayers.LonLat(junction_coords[0], junction_coords[1]);
  847. //lonlat.transform(W.Config.map.projection.remote, W.Config.map.projection.local);
  848. //let pt = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
  849. sr_x = junction.getOLGeometry().x;
  850. sr_y = junction.getOLGeometry().y;
  851. /** }
  852. else if (numNodes >= 3) {
  853. //-----------simple approximation of centre point calculated from three first points
  854. let bx = nodes_x[1];
  855. let by = nodes_y[1];
  856. let cx = nodes_x[2];
  857. let cy = nodes_y[2];
  858.  
  859. let x1 = (bx + ax) * 0.5;
  860. let y11 = (by + ay) * 0.5;
  861. let dy1 = bx - ax;
  862. let dx1 = -(by - ay);
  863. let x2 = (cx + bx) * 0.5;
  864. let y2 = (cy + by) * 0.5;
  865. let dy2 = cx - bx;
  866. let dx2 = -(cy - by);
  867. sr_x = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)/ (dx1 * dy2 - dy1 * dx2);
  868. sr_y = (sr_x - x1) * dy1 / dx1 + y11;
  869. }
  870. else {
  871. //---------- simple bounds-based calculation of center point
  872. var rbounds = new OpenLayers.Bounds();
  873. rbounds.extend(isegment.getOLGeometry().bounds);
  874.  
  875. var center = rbounds.getCenterPixel();
  876. sr_x = center.x;
  877. sr_y = center.y;
  878. }**/
  879.  
  880. let angles = [];
  881. let rr = -1;
  882. let r_ix;
  883.  
  884. for(let i=0; i<nodes_x.length; i++) {
  885.  
  886. let dx = nodes_x[i] - sr_x;
  887. let dy = nodes_y[i] - sr_y;
  888.  
  889. let rr2 = dx*dx + dy*dy;
  890. if (rr < rr2) {
  891. rr = rr2;
  892. r_ix = i;
  893. }
  894.  
  895. let angle = Math.atan2(dy, dx);
  896. angle = (360.0 + (angle * 180.0 / Math.PI));
  897. if (angle < 0.0) angle += 360.0;
  898. if (angle > 360.0) angle -= 360.0;
  899. angles.push(angle);
  900. }
  901.  
  902. radius = Math.sqrt(rr);
  903.  
  904. //---------sorting angles for calulating angle difference between two segments
  905. angles = angles.sort(function(a,b) { return a - b; });
  906. angles.push( angles[0] + 360.0);
  907. angles = angles.sort(function(a,b) { return a - b; });
  908.  
  909. let drc_color = (numNodes <= 4) ? "#0040FF" : "#002080";
  910.  
  911. let drc_point = new OpenLayers.Geometry.Point(sr_x, sr_y );
  912. let drc_circle = new OpenLayers.Geometry.Polygon.createRegularPolygon( drc_point, radius, 10 * W.map.getZoom() );
  913. let drc_feature = new OpenLayers.Feature.Vector(drc_circle, {labelText: "", labelColor: "#000000", strokeColor: drc_color, });
  914. drc_features.push(drc_feature);
  915.  
  916.  
  917. if (numNodes >= 2 && numNodes <= 4 && W.map.getZoom() >= 5) {
  918. for(let i=0; i<nodes_x.length; i++) {
  919. let ix = nodes_x[i];
  920. let iy = nodes_y[i];
  921. let startPt = new OpenLayers.Geometry.Point( sr_x, sr_y );
  922. let endPt = new OpenLayers.Geometry.Point( ix, iy );
  923. let line = new OpenLayers.Geometry.LineString([startPt, endPt]);
  924. let style = {strokeColor:drc_color, strokeWidth:2};
  925. let fea = new OpenLayers.Feature.Vector(line, {}, style);
  926. drc_features.push(fea);
  927. }
  928.  
  929. let angles_int = [];
  930. let angles_float = [];
  931. let angles_sum = 0;
  932.  
  933. for(let i=0; i<angles.length - 1; i++) {
  934.  
  935. let ang = angles[i+1] - angles[i+0];
  936. if (ang < 0) ang += 360.0;
  937. if (ang < 0) ang += 360.0;
  938.  
  939. if (ang < 135.0)
  940. ang = ang - 90.0;
  941. else
  942. ang = ang - 180.0;
  943.  
  944. angles_sum += parseInt(ang);
  945.  
  946. angles_float.push( ang );
  947. angles_int.push( parseInt(ang) );
  948. }
  949.  
  950. if (angles_sum > 45) angles_sum -= 90;
  951. if (angles_sum > 45) angles_sum -= 90;
  952. if (angles_sum > 45) angles_sum -= 90;
  953. if (angles_sum > 45) angles_sum -= 90;
  954. if (angles_sum < -45) angles_sum += 90;
  955. if (angles_sum < -45) angles_sum += 90;
  956. if (angles_sum < -45) angles_sum += 90;
  957. if (angles_sum < -45) angles_sum += 90;
  958. if (angles_sum != 0) {
  959. for(let i=0; i<angles_int.length; i++) {
  960. let a = angles_int[i];
  961. let af = angles_float[i] - angles_int[i];
  962. if ( (a < 10 || a > 20) && (af < -0.5 || af > 0.5)){
  963. angles_int[i] += -angles_sum;
  964.  
  965. break;
  966. }
  967. }
  968. }
  969.  
  970. if (numNodes == 2) {
  971. angles_int[1] = -angles_int[0];
  972. angles_float[1] = -angles_float[0];
  973. }
  974.  
  975. for(let i=0; i<angles.length - 1; i++) {
  976. let arad = (angles[i+0] + angles[i+1]) * 0.5 * Math.PI / 180.0;
  977. let ex = sr_x + Math.cos (arad) * radius * 0.5;
  978. let ey = sr_y + Math.sin (arad) * radius * 0.5;
  979.  
  980. //*** Angle Display Rounding ***
  981. let angint = Math.round(angles_float[i] * 100)/100;
  982.  
  983. let kolor = "#004000";
  984. if (angint <= -15 || angint >= 15) kolor = "#FF0000";
  985. else if (angint <= -13 || angint >= 13) kolor = "#FFC000";
  986.  
  987. let pt = new OpenLayers.Geometry.Point(ex, ey);
  988. drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (angint + "°"), labelColor: kolor } ));
  989. //drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (+angles_float[i].toFixed(2) + "°"), labelColor: kolor } ));
  990. }
  991. }
  992. else {
  993. for(let i=0; i < nodes_x.length; i++) {
  994. let ix = nodes_x[i];
  995. let iy = nodes_y[i];
  996. let startPt = new OpenLayers.Geometry.Point( sr_x, sr_y );
  997. let endPt = new OpenLayers.Geometry.Point( ix, iy );
  998. let line = new OpenLayers.Geometry.LineString([startPt, endPt]);
  999. let style = {strokeColor:drc_color, strokeWidth:2};
  1000. let fea = new OpenLayers.Feature.Vector(line, {}, style);
  1001. drc_features.push(fea);
  1002. }
  1003. }
  1004.  
  1005. let p1 = new OpenLayers.Geometry.Point( nodes_x[r_ix], nodes_y[r_ix] );
  1006. let p2 = new OpenLayers.Geometry.Point( sr_x, sr_y );
  1007. let line = new OpenLayers.Geometry.LineString([p1, p2]);
  1008. let geo_radius = line.getGeodesicLength(W.map.getProjectionObject());
  1009.  
  1010. let diam = geo_radius * 2.0;
  1011. let center_pt = new OpenLayers.Geometry.Point(sr_x, sr_y);
  1012. drc_features.push(new OpenLayers.Feature.Vector( center_pt, {labelText: (diam.toFixed(0) + "m"), labelColor: "#000000" } ));
  1013.  
  1014. }
  1015.  
  1016. }
  1017.  
  1018. drc_layer.removeAllFeatures();
  1019. drc_layer.addFeatures(drc_features);
  1020. }
  1021.  
  1022. function injectCss() {
  1023. var css = [
  1024. '.btnMoveNode {width=25px; height=25px; background-color:#92C3D3; cursor:pointer; padding:5px; font-size:14px; border:thin outset black; border-style:solid; border-width: 1px;border-radius:50%; -moz-border-radius:50%; -webkit-border-radius:50%; box-shadow:inset 0px 0px 20px -14px rgba(0,0,0,1); -moz-box-shadow:inset 0px 0px 20px -14px rgba(0,0,0,1); -webkit-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);}',
  1025. '.btnRotate { width=45px; height=45px; background-color:#92C3D3; cursor:pointer; padding: 5px; font-size:14px; border:thin outset black; border-style:solid; border-width: 1px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-moz-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);-webkit-box-shadow: inset 0px 0px 20px -14px rgba(0,0,0,1);}'
  1026. ].join(' ');
  1027. $('<style type="text/css">' + css + '</style>').appendTo('head');
  1028. }
  1029.  
  1030. })();
  1031.