WME Roundabout Angles

Draws angles for typical roundabout and overlays helper line to adjust geometry of roundabout.

  1. // ==UserScript==
  2. // @name WME Roundabout Angles
  3. // @namespace http://userscripts.org/scripts/show/440831
  4. // @description Draws angles for typical roundabout and overlays helper line to adjust geometry of roundabout.
  5. // @include https://www.waze.com/editor/*
  6. // @include https://www.waze.com/*/editor/*
  7. // @include https://editor-beta.waze.com/*
  8. // @version 1.06
  9. // @grant none
  10. // @copyright 2014 wlodek76
  11. // @copyright 2014,2016 FZ69617
  12. // ==/UserScript==
  13.  
  14. /*
  15. * Version history:
  16. *
  17. * 1.06 (20160405)
  18. * - New: Roundabount Angles layer enablement persisted in local storege.
  19. * - New: Added yellow color marker for uncertain angles.
  20. * - Change: Roundabount center taken from junction model geomentry if available.
  21. * - Change: Logic slightly modified in order to better support various roundabount types.
  22. * - Other: Minor code refactoring and cleanup.
  23. *
  24. * 1.05 (20141002)
  25. * - New: Added support for roundabouts with 1 or 2 nodes.
  26. * - Improvement: Simplified roundabout markers starts displaying at zoom level 1.
  27. * - Improvement: Minimal required zoom level to compute roundabout angles changed to 5 (was 6).
  28. * - Improvement: Light blue marker circle is now displayed only with roundabouts for which the script
  29. * can measure angles. The other roundabouts (with more than 4 nodes) are displayed a bit darker.
  30. * - Optimization: Significantly improved script execution performance.
  31. *
  32. * 1.04.1 (20141001)
  33. * - Fix: Adapted to WME v1.6-297 by FZ69617.
  34. */
  35.  
  36. var wmech_version = "1.06"
  37.  
  38. //---------------------------------------------------------------------------------------
  39. function bootstrapRoundaboutAngles()
  40. {
  41. var bGreasemonkeyServiceDefined = false;
  42.  
  43. try {
  44. bGreasemonkeyServiceDefined = (typeof Components.interfaces.gmIGreasemonkeyService === "object");
  45. }
  46. catch (err) { /* Ignore */ }
  47.  
  48. if (typeof unsafeWindow === "undefined" || ! bGreasemonkeyServiceDefined) {
  49. unsafeWindow = ( function () {
  50. var dummyElem = document.createElement('p');
  51. dummyElem.setAttribute('onclick', 'return window;');
  52. return dummyElem.onclick();
  53. }) ();
  54. }
  55.  
  56. /* begin running the code! */
  57. setInterval(DrawRoundaboutAngles, 500);
  58. // setInterval( function() {
  59. // console.time('DrawRoundaboutAngles');
  60. // DrawRoundaboutAngles();
  61. // console.timeEnd('DrawRoundaboutAngles');
  62. // }, 500 );
  63.  
  64. console.log("WME Roundabout Angles " + wmech_version + " started.");
  65. }
  66. //---------------------------------------------------------------------------------------
  67. function DrawRoundaboutAngles()
  68. {
  69. if (Waze == null || Waze.map == null || Waze.model == null || OpenLayers == null) return;
  70.  
  71. //---------get or create layer
  72. var layers = Waze.map.getLayersBy("uniqueName","__DrawRoundaboutAngles");
  73. var drc_layer;
  74. if(layers.length > 0) {
  75. drc_layer = layers[0];
  76. } else {
  77.  
  78. var drc_style = new OpenLayers.Style({
  79. fillOpacity: 0.0,
  80. strokeOpacity: 1.0,
  81. fillColor: "#FF40C0",
  82. strokeColor: "${strokeColor}",
  83.  
  84.  
  85. strokeWidth: 10,
  86. fontWeight: "bold",
  87. pointRadius: 0,
  88. label : "${labelText}",
  89. fontFamily: "Tahoma, Courier New",
  90. labelOutlineColor: "#FFFFFF",
  91. labelOutlineWidth: 3,
  92. fontColor: "${labelColor}",
  93. fontSize: "10px"
  94. });
  95.  
  96. drc_layer = new OpenLayers.Layer.Vector("Roundabout Angles", {
  97. displayInLayerSwitcher: true,
  98. uniqueName: "__DrawRoundaboutAngles",
  99. styleMap: new OpenLayers.StyleMap(drc_style)
  100. });
  101. I18n.translations.en.layers.name["__DrawRoundaboutAngles"] = "Roundabout Angles";
  102. Waze.map.addLayer(drc_layer);
  103. drc_layer.setVisibility(localStorage.WMERAEnabled == "true");
  104. }
  105. localStorage.WMERAEnabled = drc_layer.visibility;
  106.  
  107. if (drc_layer.visibility == false) {
  108. drc_layer.removeAllFeatures();
  109. return;
  110. }
  111.  
  112. if (Waze.map.zoom < 1) {
  113. drc_layer.removeAllFeatures();
  114. return;
  115. }
  116.  
  117.  
  118. //---------collect all roundabouts first
  119. var rsegments = {};
  120.  
  121.  
  122. for (var iseg in Waze.model.segments.objects) {
  123. var isegment = Waze.model.segments.get(iseg);
  124. var iattributes = isegment.attributes;
  125. var iline = isegment.geometry.id;
  126.  
  127. var irid = iattributes.junctionID;
  128. if (iline !== null && irid != undefined) {
  129. var rsegs = rsegments[irid];
  130. if (rsegs == undefined) {
  131. rsegments[irid] = rsegs = new Array();
  132. }
  133. rsegs.push(isegment);
  134. }
  135. }
  136.  
  137.  
  138. // var rcount = 0, scount = 0;
  139. // for (var irid in rsegments) {
  140. // var rsegs = rsegments[irid];
  141. // scount += rsegs.length;
  142. // ++rcount;
  143. // }
  144. // console.log("Roundabouts found: " + rcount + ", segments: " + scount);
  145.  
  146.  
  147. var drc_features = [];
  148.  
  149. //-------for each roundabout do...
  150. for (var irid in rsegments) {
  151. var rsegs = rsegments[irid];
  152.  
  153. var isegment = rsegs[0];
  154. var jsegment;
  155.  
  156. var nodes = new Array();
  157. var nodes_x = new Array();
  158. var nodes_y = new Array();
  159.  
  160. for (var j = 0; j < rsegs.length; ++j) {
  161. jsegment = rsegs[j];
  162. var jattributes = jsegment.attributes;
  163.  
  164. if (nodes.indexOf(jattributes.fromNodeID) == -1) {
  165. nodes.push(jattributes.fromNodeID);
  166. }
  167. if (nodes.indexOf(jattributes.toNodeID) == -1) {
  168. nodes.push(jattributes.toNodeID);
  169. }
  170. }
  171.  
  172. var node_objects = Waze.model.nodes.getByIds(nodes);
  173. for (var i=0; i < node_objects.length; ++i) {
  174. var node = node_objects[i];
  175.  
  176. nodes_x.push(node.geometry.x);
  177. nodes_y.push(node.geometry.y);
  178. }
  179.  
  180.  
  181. var sr_x = 0;
  182. var sr_y = 0;
  183. var radius = 0;
  184. var numNodes = nodes_x.length;
  185.  
  186.  
  187. if (numNodes >= 1) {
  188.  
  189. //-----------throw short segments
  190. /* while (nodes_x.length > 4) {
  191. var id = 0;
  192. var dmin = 99999999;
  193. for(var i=0; i<nodes_x.length; i++) {
  194. for(var j=0; j<nodes_x.length; j++) {
  195. if (i == j) continue;
  196.  
  197. var x1 = nodes_x[i];
  198. var y1 = nodes_y[i];
  199. var x2 = nodes_x[j];
  200. var y2 = nodes_y[j];
  201.  
  202. var dx = x1 - x2;
  203. var dy = y1 - y2;
  204. var d = dx*dx + dy*dy;
  205. if (d < dmin) { dmin = d; id = i; }
  206. }
  207. }
  208.  
  209. nodes_x.splice(id, 1);
  210. nodes_y.splice(id, 1);
  211. } */
  212.  
  213.  
  214. var ax = nodes_x[0];
  215. var ay = nodes_y[0];
  216.  
  217. var junction = Waze.model.junctions.get(irid);
  218. var junction_coords = junction && junction.geometry && junction.geometry.coordinates;
  219. if (junction_coords && junction_coords.length == 2) {
  220. //---------- get center point from junction model
  221. var lonlat = new OpenLayers.LonLat(junction_coords[0], junction_coords[1]);
  222. lonlat.transform(Waze.map.displayProjection, Waze.map.projection);
  223. var pt = lonlat.toPoint();
  224. sr_x = pt.x;
  225. sr_y = pt.y;
  226. }
  227. else if (numNodes >= 3) {
  228. //-----------simple approximation of centre point calculated from three first points
  229. var bx = nodes_x[1];
  230. var by = nodes_y[1];
  231. var cx = nodes_x[2];
  232. var cy = nodes_y[2];
  233.  
  234. var x1 = (bx + ax) * 0.5;
  235.  
  236.  
  237. var y11 = (by + ay) * 0.5;
  238. var dy1 = bx - ax;
  239. var dx1 = -(by - ay);
  240. var x2 = (cx + bx) * 0.5;
  241. var y2 = (cy + by) * 0.5;
  242. var dy2 = cx - bx;
  243. var dx2 = -(cy - by);
  244. sr_x = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)/ (dx1 * dy2 - dy1 * dx2);
  245. sr_y = (sr_x - x1) * dy1 / dx1 + y11;
  246. }
  247. else {
  248. //---------- simple bounds-based calculation of center point
  249. var rbounds = new OpenLayers.Bounds();
  250. rbounds.extend(isegment.geometry.bounds);
  251. rbounds.extend(jsegment.geometry.bounds);
  252.  
  253. var center = rbounds.getCenterPixel();
  254. sr_x = center.x;
  255. sr_y = center.y;
  256. }
  257.  
  258. var angles = [];
  259. var rr = -1;
  260. var r_ix;
  261.  
  262. for(var i=0; i<nodes_x.length; i++) {
  263.  
  264. var dx = nodes_x[i] - sr_x;
  265. var dy = nodes_y[i] - sr_y;
  266.  
  267. var rr2 = dx*dx + dy*dy;
  268. if (rr < rr2) {
  269. rr = rr2;
  270. r_ix = i;
  271. }
  272.  
  273. var angle = Math.atan2(dy, dx);
  274. angle = (360.0 + (angle * 180.0 / Math.PI));
  275. if (angle < 0.0) angle += 360.0;
  276. if (angle > 360.0) angle -= 360.0;
  277. angles.push(angle);
  278. }
  279.  
  280. radius = Math.sqrt(rr);
  281.  
  282.  
  283. //---------sorting angles for calulating angle difference between two segments
  284. angles = angles.sort(function(a,b) { return a - b; });
  285. angles.push( angles[0] + 360.0);
  286. angles = angles.sort(function(a,b) { return a - b; });
  287. //console.log(angles);
  288.  
  289.  
  290. var drc_color = (numNodes <= 4) ? "#0040FF" : "#002080";
  291.  
  292.  
  293. var drc_point = new OpenLayers.Geometry.Point(sr_x, sr_y );
  294. var drc_circle = new OpenLayers.Geometry.Polygon.createRegularPolygon( drc_point, radius, 10 * Waze.map.zoom );
  295. var drc_feature = new OpenLayers.Feature.Vector(drc_circle, {labelText: "", labelColor: "#000000", strokeColor: drc_color, } );
  296. drc_features.push(drc_feature);
  297.  
  298.  
  299. if (numNodes >= 2 && numNodes <= 4 && Waze.map.zoom >= 5) {
  300.  
  301.  
  302. for(var i=0; i<nodes_x.length; i++) {
  303. var ix = nodes_x[i];
  304. var iy = nodes_y[i];
  305. var startPt = new OpenLayers.Geometry.Point( sr_x, sr_y );
  306. var endPt = new OpenLayers.Geometry.Point( ix, iy );
  307. var line = new OpenLayers.Geometry.LineString([startPt, endPt]);
  308. var style = {strokeColor:drc_color, strokeWidth:2};
  309. var fea = new OpenLayers.Feature.Vector(line, {}, style);
  310. drc_features.push(fea);
  311. }
  312.  
  313. var angles_int = [];
  314. var angles_float = [];
  315. var angles_sum = 0;
  316.  
  317. for(var i=0; i<angles.length - 1; i++) {
  318.  
  319. var ang = angles[i+1] - angles[i+0];
  320. if (ang < 0) ang += 360.0;
  321. if (ang < 0) ang += 360.0;
  322.  
  323. if (ang < 135.0) {
  324. ang = ang - 90.0;
  325. }
  326. else {
  327. ang = ang - 180.0;
  328. }
  329.  
  330. angles_sum += parseInt(ang);
  331.  
  332. angles_float.push( ang );
  333. angles_int.push( parseInt(ang) );
  334. }
  335.  
  336. if (angles_sum > 45) angles_sum -= 90;
  337. if (angles_sum > 45) angles_sum -= 90;
  338. if (angles_sum > 45) angles_sum -= 90;
  339. if (angles_sum > 45) angles_sum -= 90;
  340. if (angles_sum < -45) angles_sum += 90;
  341. if (angles_sum < -45) angles_sum += 90;
  342. if (angles_sum < -45) angles_sum += 90;
  343. if (angles_sum < -45) angles_sum += 90;
  344.  
  345. if (angles_sum != 0) {
  346. for(var i=0; i<angles_int.length; i++) {
  347. var a = angles_int[i];
  348. var af = angles_float[i] - angles_int[i];
  349. if ( (a < 10 || a > 20) && (af < -0.5 || af > 0.5) ) {
  350. angles_int[i] += -angles_sum;
  351.  
  352. break;
  353. }
  354. }
  355. }
  356.  
  357. if (numNodes == 2) {
  358. angles_int[1] = -angles_int[0];
  359. angles_float[1] = -angles_float[0];
  360. }
  361.  
  362.  
  363. for(var i=0; i<angles.length - 1; i++) {
  364.  
  365. var arad = (angles[i+0] + angles[i+1]) * 0.5 * Math.PI / 180.0;
  366. var ex = sr_x + Math.cos (arad) * radius * 0.5;
  367. var ey = sr_y + Math.sin (arad) * radius * 0.5;
  368.  
  369. var angint = angles_int[i];
  370.  
  371. var kolor = "#004000";
  372. if (angint <= -15 || angint >= 15) kolor = "#FF0000";
  373. else if (angint <= -13 || angint >= 13) kolor = "#FFC000";
  374.  
  375. var pt = new OpenLayers.Geometry.Point(ex, ey);
  376. drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (angint + "°"), labelColor: kolor } ));
  377. //drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (+angles_float[i].toFixed(2) + "°"), labelColor: kolor } ));
  378. }
  379. }
  380. else {
  381.  
  382. for(var i=0; i < nodes_x.length; i++) {
  383.  
  384. var ix = nodes_x[i];
  385. var iy = nodes_y[i];
  386. var startPt = new OpenLayers.Geometry.Point( sr_x, sr_y );
  387. var endPt = new OpenLayers.Geometry.Point( ix, iy );
  388. var line = new OpenLayers.Geometry.LineString([startPt, endPt]);
  389. var style = {strokeColor:drc_color, strokeWidth:2};
  390. var fea = new OpenLayers.Feature.Vector(line, {}, style);
  391. drc_features.push(fea);
  392. }
  393. }
  394.  
  395. var p1 = new OpenLayers.Geometry.Point( nodes_x[r_ix], nodes_y[r_ix] );
  396. var p2 = new OpenLayers.Geometry.Point( sr_x, sr_y );
  397. var line = new OpenLayers.Geometry.LineString([p1, p2]);
  398. var geo_radius = line.getGeodesicLength(Waze.map.projection);
  399.  
  400. var diam = geo_radius * 2.0;
  401. var pt = new OpenLayers.Geometry.Point(sr_x, sr_y);
  402. drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (diam.toFixed(0) + "m"), labelColor: "#000000" } ));
  403.  
  404. }
  405.  
  406. }
  407. drc_layer.removeAllFeatures();
  408. drc_layer.addFeatures(drc_features);
  409. }
  410. //---------------------------------------------------------------------------------------
  411. bootstrapRoundaboutAngles();