WME Roundabout Angles

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                WME Roundabout Angles
// @namespace           http://userscripts.org/scripts/show/440831
// @description         Draws angles for typical roundabout and overlays helper line to adjust geometry of roundabout.
// @include             https://www.waze.com/editor/*
// @include             https://www.waze.com/*/editor/*
// @include             https://editor-beta.waze.com/*
// @version             1.06
// @grant               none
// @copyright           2014 wlodek76
// @copyright           2014,2016 FZ69617
// ==/UserScript==

/*
 * Version history:
 *
 * 1.06 (20160405)
 * - New: Roundabount Angles layer enablement persisted in local storege.
 * - New: Added yellow color marker for uncertain angles.
 * - Change: Roundabount center taken from junction model geomentry if available.
 * - Change: Logic slightly modified in order to better support various roundabount types.
 * - Other: Minor code refactoring and cleanup.
 *
 * 1.05 (20141002)
 * - New: Added support for roundabouts with 1 or 2 nodes.
 * - Improvement: Simplified roundabout markers starts displaying at zoom level 1.
 * - Improvement: Minimal required zoom level to compute roundabout angles changed to 5 (was 6).
 * - Improvement: Light blue marker circle is now displayed only with roundabouts for which the script
 * can measure angles. The other roundabouts (with more than 4 nodes) are displayed a bit darker.
 * - Optimization: Significantly improved script execution performance.
 *
 * 1.04.1 (20141001)
 * - Fix: Adapted to WME v1.6-297 by FZ69617.
 */

var wmech_version = "1.06"

//---------------------------------------------------------------------------------------
function bootstrapRoundaboutAngles()
{
  var bGreasemonkeyServiceDefined = false;

  try {
    bGreasemonkeyServiceDefined = (typeof Components.interfaces.gmIGreasemonkeyService === "object");
  }
  catch (err) { /* Ignore */ }

  if (typeof unsafeWindow === "undefined" || ! bGreasemonkeyServiceDefined) {
    unsafeWindow    = ( function () {
      var dummyElem = document.createElement('p');
      dummyElem.setAttribute('onclick', 'return window;');
      return dummyElem.onclick();
    }) ();
  }

    /* begin running the code! */
    setInterval(DrawRoundaboutAngles, 500);
//     setInterval( function() {
//             console.time('DrawRoundaboutAngles');
//             DrawRoundaboutAngles();
//             console.timeEnd('DrawRoundaboutAngles');
//         }, 500 );

  console.log("WME Roundabout Angles " + wmech_version + " started.");
}
//---------------------------------------------------------------------------------------
function DrawRoundaboutAngles()
{
    if (Waze == null || Waze.map == null || Waze.model == null || OpenLayers == null) return;

    //---------get or create layer
    var layers = Waze.map.getLayersBy("uniqueName","__DrawRoundaboutAngles");
    var drc_layer;
    
    if(layers.length > 0) {
        drc_layer = layers[0];
    } else {

         var drc_style = new OpenLayers.Style({
                fillOpacity: 0.0,
                strokeOpacity: 1.0,
                fillColor: "#FF40C0",
                strokeColor: "${strokeColor}",


                strokeWidth: 10,
                fontWeight: "bold",
                pointRadius: 0,
                label : "${labelText}",
                fontFamily: "Tahoma, Courier New",
                labelOutlineColor: "#FFFFFF",
                labelOutlineWidth: 3,
                fontColor: "${labelColor}",
                fontSize: "10px"
        });

        drc_layer = new OpenLayers.Layer.Vector("Roundabout Angles", {
            displayInLayerSwitcher: true,
            uniqueName: "__DrawRoundaboutAngles",
            styleMap: new OpenLayers.StyleMap(drc_style)
        });
        
        I18n.translations.en.layers.name["__DrawRoundaboutAngles"] = "Roundabout Angles";
        Waze.map.addLayer(drc_layer);
        
        drc_layer.setVisibility(localStorage.WMERAEnabled == "true");
    }
            
    localStorage.WMERAEnabled = drc_layer.visibility;

    if (drc_layer.visibility == false) {
        drc_layer.removeAllFeatures();
        return;
    }

    if (Waze.map.zoom < 1) {
        drc_layer.removeAllFeatures();
        return;
    }


    //---------collect all roundabouts first
    var rsegments = {};


    for (var iseg in Waze.model.segments.objects) {
        var isegment = Waze.model.segments.get(iseg);
        var iattributes = isegment.attributes;
        var iline = isegment.geometry.id;

        var irid = iattributes.junctionID;
        
        if (iline !== null && irid != undefined) {
            var rsegs = rsegments[irid];
            if (rsegs == undefined) {
                rsegments[irid] = rsegs = new Array();
            }
            rsegs.push(isegment);
        }
    }


//     var rcount = 0, scount = 0;
//     for (var irid in rsegments) {
//         var rsegs = rsegments[irid];
//         scount += rsegs.length;
//         ++rcount;
//     }
//     console.log("Roundabouts found: " + rcount + ", segments: " + scount);


    var drc_features = [];

    //-------for each roundabout do...
    for (var irid in rsegments) {
        var rsegs = rsegments[irid];

        var isegment = rsegs[0];
        var jsegment;

        var nodes = new Array();
        var nodes_x = new Array();
        var nodes_y = new Array();

        for (var j = 0; j < rsegs.length; ++j) {
            jsegment = rsegs[j];
            var jattributes = jsegment.attributes;

            if (nodes.indexOf(jattributes.fromNodeID) == -1) {
                nodes.push(jattributes.fromNodeID);
            }
            if (nodes.indexOf(jattributes.toNodeID) == -1) {
                nodes.push(jattributes.toNodeID);
            }
        }

        var node_objects = Waze.model.nodes.getByIds(nodes);
        for (var i=0; i < node_objects.length; ++i) {
            var node = node_objects[i];

            nodes_x.push(node.geometry.x);
            nodes_y.push(node.geometry.y);
        }


        var sr_x   = 0;
        var sr_y   = 0;
        var radius = 0;
        var numNodes = nodes_x.length;


        if (numNodes >= 1) {

            //-----------throw short segments
            /* while (nodes_x.length > 4) {
                var id = 0;
                var dmin = 99999999;
                for(var i=0; i<nodes_x.length; i++) {
                    for(var j=0; j<nodes_x.length; j++) {
                        if (i == j) continue;

                        var x1 = nodes_x[i];
                        var y1 = nodes_y[i];
                        var x2 = nodes_x[j];
                        var y2 = nodes_y[j];

                        var dx = x1 - x2;
                        var dy = y1 - y2;
                        var d = dx*dx + dy*dy;
                        if (d < dmin) { dmin = d; id = i; }
                    }
                }

                nodes_x.splice(id, 1);
                nodes_y.splice(id, 1);
            } */


            var ax = nodes_x[0];
            var ay = nodes_y[0];

            var junction = Waze.model.junctions.get(irid);
            var junction_coords = junction && junction.geometry && junction.geometry.coordinates;
            
            if (junction_coords && junction_coords.length == 2) {
                //---------- get center point from junction model
                var lonlat = new OpenLayers.LonLat(junction_coords[0], junction_coords[1]);
                lonlat.transform(Waze.map.displayProjection, Waze.map.projection);
                var pt = lonlat.toPoint();
                sr_x = pt.x;
                sr_y = pt.y;
            }
            else if (numNodes >= 3) {
                //-----------simple approximation of centre point calculated from three first points
                var bx = nodes_x[1];
                var by = nodes_y[1];
                var cx = nodes_x[2];
                var cy = nodes_y[2];

                var x1 = (bx + ax) * 0.5;


                var y11 = (by + ay) * 0.5;
                var dy1 = bx - ax;
                var dx1 = -(by - ay);
                var x2 = (cx + bx) * 0.5;
                var y2 = (cy + by) * 0.5;
                var dy2 = cx - bx;
                var dx2 = -(cy - by);
                sr_x = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)/ (dx1 * dy2 - dy1 * dx2);
                sr_y = (sr_x - x1) * dy1 / dx1 + y11;
            }
            else {
                //---------- simple bounds-based calculation of center point
                var rbounds = new OpenLayers.Bounds();
                rbounds.extend(isegment.geometry.bounds);
                rbounds.extend(jsegment.geometry.bounds);

                var center = rbounds.getCenterPixel();
                sr_x = center.x;
                sr_y = center.y;
            }

            var angles = [];
            var rr = -1;
            var r_ix;

            for(var i=0; i<nodes_x.length; i++) {

                var dx = nodes_x[i] - sr_x;
                var dy = nodes_y[i] - sr_y;

                var rr2 = dx*dx + dy*dy;
                if (rr < rr2) {
                    rr = rr2;
                    r_ix = i;
                }

                var angle = Math.atan2(dy, dx);
                angle = (360.0 + (angle * 180.0 / Math.PI));
                if (angle < 0.0) angle += 360.0;
                if (angle > 360.0) angle -= 360.0;
                angles.push(angle);
            }

            radius = Math.sqrt(rr);


            //---------sorting angles for calulating angle difference between two segments
            angles = angles.sort(function(a,b) { return a - b; });
            angles.push( angles[0] + 360.0);
            angles = angles.sort(function(a,b) { return a - b; });
            //console.log(angles);


            var drc_color = (numNodes <= 4) ? "#0040FF" : "#002080";


            var drc_point = new OpenLayers.Geometry.Point(sr_x, sr_y );
            var drc_circle = new OpenLayers.Geometry.Polygon.createRegularPolygon( drc_point, radius, 10 * Waze.map.zoom );
            var drc_feature = new OpenLayers.Feature.Vector(drc_circle, {labelText: "", labelColor: "#000000", strokeColor: drc_color, }  );
            drc_features.push(drc_feature);


            if (numNodes >= 2 && numNodes <= 4 && Waze.map.zoom >= 5) {


               for(var i=0; i<nodes_x.length; i++) {
                    var ix = nodes_x[i];
                    var iy = nodes_y[i];
                    var startPt   = new OpenLayers.Geometry.Point( sr_x, sr_y );
                    var endPt     = new OpenLayers.Geometry.Point( ix, iy );
                    var line      = new OpenLayers.Geometry.LineString([startPt, endPt]);
                    var style     = {strokeColor:drc_color, strokeWidth:2};
                    var fea       = new OpenLayers.Feature.Vector(line, {}, style);
                    drc_features.push(fea);
               }

               var angles_int = [];
               var angles_float = [];
               var angles_sum = 0;

               for(var i=0; i<angles.length - 1; i++) {

                   var ang = angles[i+1] - angles[i+0];
                   if (ang < 0) ang += 360.0;
                   if (ang < 0) ang += 360.0;

                   if (ang < 135.0) {
                      ang = ang - 90.0;
                   }
                   else {
                      ang = ang - 180.0;
                   }

                   angles_sum += parseInt(ang);

                   angles_float.push( ang );
                   angles_int.push( parseInt(ang) );
               }

               if (angles_sum > 45) angles_sum -= 90;
               if (angles_sum > 45) angles_sum -= 90;
               if (angles_sum > 45) angles_sum -= 90;
               if (angles_sum > 45) angles_sum -= 90;
               if (angles_sum < -45) angles_sum += 90;
               if (angles_sum < -45) angles_sum += 90;
               if (angles_sum < -45) angles_sum += 90;
               if (angles_sum < -45) angles_sum += 90;

               if (angles_sum != 0) {
                   for(var i=0; i<angles_int.length; i++) {
                       var a = angles_int[i];
                       var af = angles_float[i] - angles_int[i];
                       if ( (a < 10 || a > 20) && (af < -0.5 || af > 0.5) )  {
                           angles_int[i] += -angles_sum;

                           break;
                       }
                   }
               }

               if (numNodes == 2) {
                   angles_int[1] = -angles_int[0];
                   angles_float[1] = -angles_float[0];
               }


               for(var i=0; i<angles.length - 1; i++) {

                   var arad = (angles[i+0] + angles[i+1]) * 0.5 * Math.PI / 180.0;
                   var ex = sr_x + Math.cos (arad) * radius * 0.5;
                   var ey = sr_y + Math.sin (arad) * radius * 0.5;

                   var angint = angles_int[i];

                   var kolor = "#004000";
                   if (angint <= -15 || angint >= 15) kolor = "#FF0000";
                   else if (angint <= -13 || angint >= 13) kolor = "#FFC000";

                   var pt = new OpenLayers.Geometry.Point(ex, ey);
                   drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (angint + "°"), labelColor: kolor } ));
                   //drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (+angles_float[i].toFixed(2) + "°"), labelColor: kolor } ));
               }
            }
            else {

               for(var i=0; i < nodes_x.length; i++) {

                    var ix = nodes_x[i];
                    var iy = nodes_y[i];
                    var startPt   = new OpenLayers.Geometry.Point( sr_x, sr_y );
                    var endPt     = new OpenLayers.Geometry.Point( ix, iy );
                    var line      = new OpenLayers.Geometry.LineString([startPt, endPt]);
                    var style     = {strokeColor:drc_color, strokeWidth:2};
                    var fea       = new OpenLayers.Feature.Vector(line, {}, style);
                    drc_features.push(fea);
               }               
            }

            var p1   = new OpenLayers.Geometry.Point( nodes_x[r_ix], nodes_y[r_ix] );
            var p2   = new OpenLayers.Geometry.Point( sr_x, sr_y );
            var line = new OpenLayers.Geometry.LineString([p1, p2]);
            var geo_radius = line.getGeodesicLength(Waze.map.projection);

            var diam = geo_radius * 2.0;
            var pt = new OpenLayers.Geometry.Point(sr_x, sr_y);
            drc_features.push(new OpenLayers.Feature.Vector( pt, {labelText: (diam.toFixed(0) + "m"), labelColor: "#000000" } ));

        }

    }
        
    drc_layer.removeAllFeatures();
    drc_layer.addFeatures(drc_features);
}
//---------------------------------------------------------------------------------------
bootstrapRoundaboutAngles();