Get a sneak peek at whats next for Permanent Hazards on our April 7th Office Hours!
Discussion for the unofficial, community-developed addons, extensions and scripts built for the Waze Map Editor.

The official index of these tools is the Community Plugins, Extensions and Tools wiki page.
Post by doctorkb
Of course, a reference to the "Waze claims it isn't and testing confirms that it isn't" may also be suitable to have us revise our practice and documentation...

Until then, this new version of JNF is not approved for use in Canada, as it is not in agreement with our local documentation. The previous version still works fine and is approved for use.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
request of Waze staff
A source citation would be appreciated. I've not seen this request in my travels.

I've been disabling these u-turns manually, then with the use of JNF (clicking on the endnode, then hitting 'q'), then finally with the use of Toolbox. Unless you actually selected the end node, JNF did nothing to them, in my experience -- which sounds to be a change in behaviour now.

I'm sorry... but your "because I said so" doesn't hold much water -- unless you can reference some thread that has extensive testing and/or a semi-public (e.g. GC forum) or public request from staff, I'm sticking to my guns.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
Taco909 wrote:
bgodette wrote:
mikenit wrote:Since a problem/modification in WME, JNF and others scripts are broken.

WARNING: keys W and Q fall back to the original function in the WME.
You're not running 0.0.9.0 or 0.0.9.1 as JNF is unaffected, since 0.0.9.0 was compatible with the Beta code that is now Production. The new Beta code is a different story.
I'm running 0.0.8.2 and that is the only version available on Userscripts (yes, it is back up).
This is the .user.js file from the distribution.

See if you can use this in GM -- I have it working in TM.

Code: Select all

// ==UserScript==
// @name                WME Junction Node Fixer
// @description         Creates a new editor hotkey to lock turns, fix reverse connectivity, and restore original restricted turns.
// @include             https://*.waze.com/editor/*
// @version             0.0.9.1
// ==/UserScript==

WME_JNF_Version = "v0.0.9.1";

function WME_JNF_Bootstrap()
{
  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! */
  setTimeout(WME_JNF_Init, 500);
}

var matched = false;
var WME_Version = undefined;

// Feature detect + local reference
var storage, fail, uid;
var options = {};

var WCENC = null;
var WCSAVE = null;
var WCCHAT = null;
var WCLU = null;
var WCUR = null;
var WCMP = null;

function WME_JNF_PatchAndReload() {
  var patch;
}

function WME_JNF_SaveEnd(b) {
  console.log("WME-JNF: Save %s", b.success ? "succeeded" : "failed");
  if (b.success) {
    WCSAVE.controller.reload();
  }
}


function WME_JNF_smn(s1, s2)
{
  var pmap = [0, 10, 11, 15, 14, 1, 13, 12, 8, 0, 3, 0, 0, 0, 0, 0, 2, 7, 5, 4, 6, 9];
  var mod = null;
  if (s1.isGeometryEditable() && !s2.isGeometryEditable()) {
    console.log("only s1 can be modified");
    mod = s1;
  } else if (!s1.isGeometryEditable() && s2.isGeometryEditable()) {
    console.log("only s2 can be modified");
    mod = s2;
  } else if (s1.isGeometryEditable() && s2.isGeometryEditable()) {
    /* pick one */
    if (pmap[s1.attributes.roadType] < pmap[s2.attributes.roadType]) {
      console.log("s1 lower type");
      mod = s1;
    } else if (pmap[s1.attributes.roadType] > pmap[s2.attributes.roadType]) {
      console.log("s2 lower type");
      mod = s2;
    } else {
      if (s1.attributes.length > s2.attributes.length) {
        console.log("s1 longer");
        mod = s1;
      } else if (s1.attributes.length < s2.attributes.length) {
        console.log("s2 longer");
        mod = s2;
      } else {
        if (s1.attributes.createdOn > s2.attributes.createdOn) {
          console.log("s1 newer");
          mod = s1;
        } else if (s1.attributes.createdOn < s2.attributes.createdOn) {
          console.log("s2 newer");
          mod = s2;
        } else {
          if (s1.getID() > s2.getID()) {
            console.log("s1 higher id");
            mod = s1;
          } else {
            console.log("s2 higher id");
            mod = s2;
          }
        }
      }
    }
  } else {
    console.log("cannot modify either.");
  }
  if (mod) {
    var point;
    if (mod.geometry.components.length > 2) {
      console.log("Splitting: " + mod.getID() + " on geo point " + (Math.ceil(mod.geometry.components.length / 2) - 1) + " of " + mod.geometry.components.length);
      point = mod.geometry.components[Math.ceil(mod.geometry.components.length / 2) - 1];
    } else {
      point = mod.getCenter();
      console.log("Splitting:", mod.getID(), "at center.");
    }
    W.model.actionManager.add(new Waze.Action.SplitSegments(mod, {splitAtPoint: point}));
  }
}

function WME_JNF_CleanRBT(jct) {
  var roadTypes = { "street": 1,      "primary": 2,   "freeway": 3,   "ramp": 4,
                    "trail": 5,       "major": 6,     "minor": 7,     "dirt": 8,
                    "boardwalk": 10,  "stairway": 16, "private": 17,  "railroad": 18,
                    "runway": 19,     "parking": 20,  "service": 21};
  var typenames = { 1: "street",      2: "primary",   3: "freeway",   4: "ramp",
                    5: "trail",       6: "major",     7: "minor",     8: "dirt",
                    10: "boardwalk",  16: "stairway", 17: "private",  18: "railroad",
                    19: "runway",     20: "parking",  21: "service"};
  var prec = [4, 6, 7, 2, 1, 21, 17, 20, 8];
  if (jct.valid == true) {
    var types = {};
    var roadtype = false;
    var cities = {};
    var i = 0;
    var cityid = 0;
    var street = null;
    var city = null;
    var state = null;
    var country = null;
    var update = true;
    var street_updated = false;
    var type_updated = false;
    var nodes = {};
    
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      for (var i = 0; i < seg.geometry.components.length; i++)
        if (!onScreen(seg))
          update = false;
    });

    if (update == false)
      return;
    
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      if (seg.attributes.primaryStreetID) {
        street = W.model.streets.get(seg.attributes.primaryStreetID);
        city = W.model.cities.get(street.cityID);
        if (city) {
          if (!cities[street.cityID])
            cities[street.cityID] = 0;
          if (!city.isEmpty)
            cities[street.cityID] += 100;
        }
      }
      nodes[seg.attributes.toNodeID] = W.model.nodes.get(seg.attributes.toNodeID);
      nodes[seg.attributes.fromNodeID] = W.model.nodes.get(seg.attributes.fromNodeID);
    });
    Object.forEach(nodes, function(k, node) {
      node.attributes.segIDs.forEach(function(csegid) {
        var cseg = W.model.segments.get(csegid);
        if (!cseg.attributes.junctionID) {
          if (cseg.attributes.roadType != roadTypes["freeway"]) {
            if (!types[cseg.attributes.roadType])
              types[cseg.attributes.roadType] = 0;
            if (cseg.attributes.fwdDirection)
              types[cseg.attributes.roadType] += 1;
            if (cseg.attributes.revDirection)
              types[cseg.attributes.roadType] += 1;
            if (cseg.attributes.primaryStreetID) {
              street = W.model.streets.get(cseg.attributes.primaryStreetID);
              city = W.model.cities.get(street.cityID);
              if (city) {
                if (!cities[street.cityID])
                  cities[street.cityID] = 0;
                if (city.isEmpty) {
                  cities[street.cityID] += 1; 
                } else {
                  cities[street.cityID] += 2;
                }
              }
            }
          } 
        }
      });
    });
    i = 0;
    Object.forEach(cities, function(k, v) {
      if (i < v) {
        i = v;
        cityid = k;
      }
    });
    street = W.model.streets.getByAttributes({isEmpty: true, cityID: cityid}).first();
    city = W.model.cities.get(cityid);
    if (city)
      country = W.model.countries.get(city.countryID);
    state = null;
    if (city && city.stateID)
      state = W.model.states.get(city.stateID);
    var j;
    for (i = 0; i < prec.length && !roadtype; i++) {
      if (city.countryID == 234 && prec[i] == 4)
        continue;
      if (prec[i] in types) {
        if (types[prec[i]] > 3 || (types[prec[i]] && city.countryID == 234)) {
          roadtype = prec[i];
        } else {
          for (j = i+1; j < prec.length && !roadtype; j++) {
            if (types[prec[j]] > 1) {
              roadtype = prec[j];
            }
          }
        }
        if (!roadtype)
          roadtype = prec[i];
      }
    }
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      if (seg.attributes.roadType != roadtype) {
        W.model.actionManager.add(new W.Action.UpdateObject(seg, {roadType: roadtype}));
        if (!type_updated) {
          console.log("JNF_RBT: road type: " + roadtype + " " + typenames[roadtype]);
          type_updated = true;
        }
      }
      if (!seg.attributes.primaryStreetID || (street && seg.attributes.primaryStreetID != street.id)) {
        W.model.actionManager.add(new W.Action.UpdateSegmentAddress(seg, {countryID: city.countryID, stateID: city.stateID, cityName: city.name, emptyStreet: true}));
        if (!street_updated) {
          if (state.name != "Other")
            console.log("JNF_RBT: " + city.name + ", " + state.name + ", " + country.name);
          else
            console.log("JNF_RBT: " + city.name + ", " + country.name);
          street = W.model.streets.getByAttributes({isEmpty: true, cityID: cityid}).first();
          console.log("JNF_RBT: street: %o ", street);
          street_updated = true;
        }
      }
    });
    Object.forEach(nodes, function(k, node) {
      WME_JNF_FixNode(node, false);
    });
  }
}

function WME_JNF_DAT(a) {
  if (!a.enabled)
    return;
  WME_JNF_FixNode(a.selectedFeature, true);
}

function onScreen(obj) {
  if (obj.geometry) {
    return(W.map.getExtent().intersectsBounds(obj.geometry.getBounds()));
  }
  return(false);
}

WME_JNF_FixNode = function(node, doJunctions) {
  if (!node)
    return;
  if (!node.type)
    return;
  if (node.type != "node")
    return;
  if (node.areConnectionsEditable() && onScreen(node)) {
    connections = {};
    junctions = {};
    
    for (var i = 0; i < node.attributes.segIDs.length; i++) {
      var seg = W.model.segments.get(node.attributes.segIDs[i]);
      if (seg) {
        if (seg.attributes.toNodeID == seg.attributes.fromNodeID) {
          if (seg.attributes.junctionID) {
            console.log("single node rb");
          } else {
            console.log("single node loop");
          }
          var seg1geo = seg.geometry.clone();
          var seg2geo = seg.geometry.clone();
          var seg3geo = seg.geometry.clone();
          var mod3 = seg.geometry.components.length % 3;
          for (var i = 0; i < seg.geometry.components.length / 3 - 1; i++) {
            seg1geo.components.pop();
            seg1geo.components.pop();
            seg2geo.components.pop();
            seg2geo.components.shift();
            seg3geo.components.shift();
            seg3geo.components.shift();
          }
          if (mod3 == 2) {
            seg1geo.components.pop();
            seg3geo.components.shift();
          }
          if (mod3 == 0) {
            seg1geo.components.pop();
            seg1geo.components.pop();
            seg3geo.components.shift();
            seg3geo.components.shift();
          }
          var newseg1, newseg3, ns1ls, ns3ls;
          if (node.attributes.connections) {
            newseg1 = new Waze.Feature.Vector.Segment(seg1geo);
            newseg3 = new Waze.Feature.Vector.Segment(seg3geo);
          } else {
            ns1ls = new OpenLayers.Geometry.LineString(seg1geo.getVertices());
            ns3ls = new OpenLayers.Geometry.LineString(seg3geo.getVertices());
            newseg1 = new Waze.Feature.Vector.Segment;
            newseg3 = new Waze.Feature.Vector.Segment;
          }
          newseg1.copyAttributes(seg);
          newseg3.copyAttributes(seg);
          newseg1.attributes.junctionID = null;
          newseg3.attributes.junctionID = null;
          newseg1.attributes.fromNodeID = null;
          newseg3.attributes.fromNodeID = null;
          newseg1.attributes.toNodeID = null;
          newseg3.attributes.toNodeID = null;
          if (!node.attributes.connections) {
            newseg1.geometry = ns1ls;
            newseg3.geometry = ns3ls;
            newseg1.setID(null);
            newseg3.setID(null);
            console.log("ns1:", newseg1);
            console.log("ns3:", newseg3);
          }
          var joinsegs = [];
          joinsegs.push(newseg1);
          joinsegs.push(seg);
          console.log("Disconnect:", seg.getID(), seg, "from:", node.getID(), node);
          W.model.actionManager.add(new W.Action.DisconnectSegment(seg, node));
          console.log("Disconnect:", seg.getID(), seg, "from:", node.getID(), node);
          W.model.actionManager.add(new W.Action.DisconnectSegment(seg, node));
          console.log("W.A.AddSegment(W.F.V.S(seg1geo))");
          W.model.actionManager.add(new W.Action.AddSegment(newseg1));
          console.log("W.A.AddSegment(W.F.V.S(seg3geo))");
          W.model.actionManager.add(new W.Action.AddSegment(newseg3));
          console.log("ns1:", newseg1);
          console.log("ns3:", newseg3);
          console.log("UpdateSegmentGeometry:", seg.getID(), seg);
          W.model.actionManager.add(new W.Action.UpdateSegmentGeometry(seg, seg.geometry, seg2geo));
          W.model.actionManager.add(new W.Action.ConnectSegment(node, newseg1));
          W.model.actionManager.add(new W.Action.ConnectSegment(node, newseg3));
          console.log("Add Node at", seg1geo.components.last());
          W.model.actionManager.add(new W.Action.AddNode(seg1geo.components.last(), joinsegs));
          joinsegs = []
          joinsegs.push(seg);
          joinsegs.push(newseg3);
          console.log("Add Node at", seg3geo.components.first());
          W.model.actionManager.add(new W.Action.AddNode(seg3geo.components.first(), joinsegs));
          W.model.actionManager.add(new W.Action.UpdateObject(newseg1, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new W.Action.UpdateObject(seg, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new W.Action.UpdateObject(newseg3, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(newseg1.getToNode(), true));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(newseg1.getFromNode(), true));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(seg.getToNode(), true));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(seg.getFromNode(), true));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(newseg3.getToNode(), true));
          W.model.actionManager.add(new W.Action.ModifyAllConnections(newseg3.getFromNode(), true));
        }
        if (!seg.isDeleted()) {
          // store any roundabouts we see
          if (seg.attributes.junctionID) {
            junctions[seg.attributes.junctionID] = W.model.junctions.get(seg.attributes.junctionID);
          }

          // terminate unterminated dead-ends
          var segments = [];
          segments.push(seg);
          if (seg.attributes.toNodeID == null) {
            W.model.actionManager.add(new W.Action.AddNode(seg.geometry.components.last(), segments));
          }
          if (seg.attributes.fromNodeID == null) {
            W.model.actionManager.add(new W.Action.AddNode(seg.geometry.components.first(), segments));
          }

          var toNode = seg.getToNode();
          var fromNode = seg.getFromNode();
          if (toNode && fromNode && !toNode.isDeleted() && !fromNode.isDeleted()) {
            if (onScreen(toNode) && onScreen(fromNode)) {
              if ((seg.attributes.fwdDirection == false || seg.attributes.revDirection == false) && (toNode.attributes.segIDs.length < 2 || fromNode.attributes.segIDs.length < 2))
              {
                console.log("JNF: updating dead-end segment " + seg.getID() + " to two-way");
                W.model.actionManager.add(new W.Action.UpdateObject(seg, {fwdDirection: true, revDirection: true}));
              }
              if (toNode.attributes.connections) {
                // old editor
                if (toNode.attributes.segIDs.length < 2 && !toNode.isTurnAllowed(seg, seg)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new W.Action.ModifyConnection(seg.getID(), toNode, seg.getID(), true));
                  if (!seg.attributes.fwdTurnsLocked) {
                     W.model.actionManager.add(new W.Action.UpdateObject(seg, {fwdTurnsLocked: true}));
                  }
                }
                if (fromNode.attributes.segIDs.length < 2 && !fromNode.isTurnAllowed(seg, seg)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new W.Action.ModifyConnection(seg.getID(), fromNode, seg.getID(), true));
                  if (!seg.attributes.revTurnsLocked) {
                     W.model.actionManager.add(new W.Action.UpdateObject(seg, {revTurnsLocked: true}));
                  }
                }
              } else {
                // beta editor
                if (toNode.attributes.segIDs.length < 2 && !seg.isTurnAllowed(seg, toNode)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new W.Action.ModifyConnection(seg.getID(), toNode, seg.getID(), true));
                }
                if (fromNode.attributes.segIDs.length < 2 && !seg.isTurnAllowed(seg, fromNode)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new W.Action.ModifyConnection(seg.getID(), fromNode, seg.getID(), true));
                }
              }
            }
            if (node.attributes.segIDs.length > 1) {
              // disable u-turns
              var turnAllowed;
              if (node.attributes.connections) {
                // old editor
                turnAllowed = node.isTurnAllowed(seg, seg);
              } else {
                // beta editor
                turnAllowed = seg.isTurnAllowed(seg, node);
              }
              if (turnAllowed) {
                console.log("Disabling U-Turn at", node.getID(), "on", seg.getID());
                W.model.actionManager.add(new W.Action.ModifyConnection(seg.getID(), node, seg.getID(), false));
              }
            }
          }
        }
      }
    }
    
    if (node.attributes.segIDs.length > 1) {
      var seg1, seg2;
      for (var i = 0; i < node.attributes.segIDs.length - 1; i++) {
        seg1 = W.model.segments.get(node.attributes.segIDs[i]);
        for (var j = i + 1; j < node.attributes.segIDs.length; j++) {
          seg2 = W.model.segments.get(node.attributes.segIDs[j]);
          if (seg1.isDeleted() == false && seg2.isDeleted() == false) {
            var fwd_t;
            var rev_t;
            var fwd_a;
            var rev_a;
            if (node.attributes.connections) {
              fwd_t = node.isTurnAllowed(seg1, seg2);
              rev_t = node.isTurnAllowed(seg2, seg1);
            } else {
              fwd_t = seg1.isTurnAllowed(seg2, node);
              rev_t = seg2.isTurnAllowed(seg1, node);
            }
            fwd_a = node.isTurnAllowedBySegDirections(seg1, seg2);
            rev_a = node.isTurnAllowedBySegDirections(seg2, seg1);
            if (fwd_t && !fwd_a) {
              console.log("Disabling RevCon at", node.getID(), "into", seg2.getID());
              W.model.actionManager.add(new W.Action.ModifyConnection(seg1.getID(), node, seg2.getID(), false));
            }
            if (rev_t && !rev_a) {
              console.log("Disabling RevCon at", node.getID(), "into", seg1.getID());
              W.model.actionManager.add(new W.Action.ModifyConnection(seg2.getID(), node, seg1.getID(), false));
            }
            if ((seg1.attributes.fromNodeID == seg2.attributes.fromNodeID && seg1.attributes.toNodeID == seg2.attributes.toNodeID) ||
                (seg1.attributes.fromNodeID == seg2.attributes.toNodeID && seg1.attributes.toNodeID == seg2.attributes.fromNodeID)) {
              console.log("sid:", seg1.getID(), "and sid:", seg2.getID(), "connected to same nodes:", seg1.attributes.fromNodeID, seg1.attributes.toNodeID);
              WME_JNF_smn(seg1, seg2);
            }
          }
        }
        if (!seg1.isDeleted() && !seg1.areTurnsLocked(node)) {
          var attr = seg1.getTurnsLockAttribute(node);
          var dict = {}
          dict[attr] = true;
          console.log("Locking Turns at", node.getID(), "on", seg1.getID());
          W.model.actionManager.add(new W.Action.UpdateObject(seg1, dict));
        }
      }
      if (!seg2.isDeleted() && !seg2.areTurnsLocked(node)) {
        var attr = seg2.getTurnsLockAttribute(node);
        var dict = {}
        dict[attr] = true;
        console.log("Locking Turns at", node.getID(), "on", seg2.getID());
        W.model.actionManager.add(new W.Action.UpdateObject(seg2, dict));
      }
    }

    if (doJunctions) {
      // clean up roundabouts
      Object.forEach(junctions, function(i, j) {
        WME_JNF_CleanRBT(j);
      });
    }
    
    // refresh turn arrows
    WCENC.toggleShowAllArrows();
    WCENC.toggleShowAllArrows();
  }
}

function WME_JNF_CheckAPI() {
  if (typeof(Waze) != "object") {
    matched = "Waze";
    return false;
  }
  if (typeof(W.model) != "object") {
    matched = "W.model";
    return false;
  }
  if (typeof(W.map) != "object") {
    matched = "W.map";
    return false;
  }
  if (typeof(W.map.controls) != "object") {
    matched = "W.map.controls";
    return false;
  }
  if (typeof(W.map.controls[0]) != "object") {
    matched = "W.map.controls[0]";
    return false;
  }
  if (typeof(W.map.controls[0].displayClass) != "string") {
    matched = "W.map.controls[0].displayClass";
    return false;
  }
  Object.forEach(W.map.controls, function(k, v) {
    if (v.displayClass == "WazeControlEditNodeConnections") {
      WCENC = v;
    }
    if (v.displayClass == "WazeControlSave") {
      WCSAVE = v;
    }
    if (v.displayClass == "WazeControlChat") {
      WCCHAT = v;
    }
    if (v.displayClass == "WazeControlMapProblems") {
      WCMP = v;
    }
    if (v.displayClass == "WazeControlUpdateRequests") {
      WCUR = v;
    }
    if (v.displayClass == "WazeControlLiveUsers") {
      WCLU = v;
    }
  });
  if (typeof(WCENC) != "object") {
    matched = "WCENC";
    return false;
  }
  if (typeof(WCSAVE) != "object") {
    matched = "WCSAVE";
    return false;
  }
  if (typeof(WCCHAT) != "object") {
    matched = "WCCHAT";
    return false;
  }
  if (typeof(WCMP) != "object") {
    matched = "WCMP";
    return false;
  }
  if (typeof(WCUR) != "object") {
    matched = "WCUR";
    return false;
  }
  if (typeof(WCLU) != "object") {
    matched = "WCLU";
    return false;
  }
  if (typeof(Waze.Config) != "object") {
    matched = "Waze.Config";
    return false;
  }
  if (typeof(Waze.Config.cameras) != "object") {
    matched = "Waze.Config.cameras";
    return false;
  }
  if (typeof(Waze.Config.cameras.minDisplayZoom) != "number") {
    matched = "Waze.Config.cameras.minDisplayZoom";
    return false;
  }
  if (typeof(W.model.cameras) != "object") {
    matched = "W.model.cameras";
    return false;
  }
  if (typeof(W.model.cameras.minZoom) != "number") {
    matched = "W.model.cameras.minZoom";
    return false;
  }
  if (typeof(W.map.toggleFullscreen) != "function") {
    matched = "W.map.toggleFullscreen";
    return false;
  }
  if (typeof(WCENC.showAllArrows) != "boolean") {
    matched = "WCENC.showAllArrows";
    return false;
  }
  if (typeof(WCENC.showArrows) != "boolean") {
    matched = "WCENC.showArrows";
    return false;
  }
  if (typeof(WCENC.toggleShowAllArrows) != "function") {
    matched = "WCENC.toggleShowAllArrows";
    return false;
  }
  if (typeof(WCSAVE.controller) != "object") {
    matched = "WCSAVE.controller";
    return false;
  }
  if (typeof(WCSAVE.controller.events) != "object") {
    matched = "WCSAVE.controller.events";
    return false;
  }
  if (typeof(WCSAVE.controller.events.register) != "function") {
    matched = "WCSAVE.controller.events.register";
    return false;
  }
  if (typeof(W.map.DefaultPanInPixel) != "number") {
    matched = "W.map.DefaultPanInPixel";
    return false;
  }
  if (typeof(Waze.accelerators) != "object") {
    if (typeof(Waze.Accelerators) != "object") {
      matched = "Waze.accelerators";
      return false;
    } else {
      Waze.accelerators = Waze.Accelerators;
    }
  }
  if (typeof(Waze.accelerators.events) != "object") {
    matched = "Waze.accelerators.events";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners) != "object") {
    matched = "Waze.accelerators.events.listeners";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections) != "object") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections[0]) != "object") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections[0]";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections[0].func) != "function") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections[0].func";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections) != "object") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections[0]) != "object") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections[0]";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections[0].func) != "function") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections[0].func";
    return false;
  }
  if (typeof(W.map.getExtent) != "function") {
    matched = "W.map.getExtent";
    return false;
  }
  var tstvar = W.map.getExtent();
  if (typeof(tstvar) != "object") {
    matched = "W.map.getExtent()";
    return false;
  }
  if (typeof(tstvar.toGeometry) != "function") {
    matched = "W.map.getExtent().toGeometry";
    return false;
  }
  var tstvar = tstvar.toGeometry();
  if (typeof(tstvar) != "object") {
    matched = "W.map.getExtent().toGeometry() == object";
    return false;
  }
  if (typeof(tstvar.containsPoint) != "function") {
    matched = "W.map.getExtent().toGeometry().containsPoint";
    return false;
  }
  if (typeof(W.model.segments) != "object") {
    matched = "W.model.segments";
    return false;
  }
  if (typeof(W.model.segments.get) != "function") {
    matched = "W.model.segments.get";
    return false;
  }
  if (typeof(W.model.nodes) != "object") {
    matched = "W.model.nodes";
    return false;
  }
  if (typeof(W.model.nodes.get) != "function") {
    matched = "W.model.nodes.get";
    return false;
  }
  if (typeof(W.model.junctions) != "object") {
    matched = "W.model.junctions";
    return false;
  }
  if (typeof(W.model.junctions.get) != "function") {
    matched = "W.model.junctions.get";
    return false;
  }
  if (typeof(W.model.streets) != "object") {
    matched = "W.model.streets";
    return false;
  }
  if (typeof(W.model.streets.get) != "function") {
    matched = "W.model.streets.get";
    return false;
  }
  if (typeof(W.model.streets.getByAttributes) != "function") {
    matched = "W.model.streets.getByAttributes";
    return false;
  }
  if (typeof(W.model.cities) != "object") {
    matched = "W.model.cities";
    return false;
  }
  if (typeof(W.model.cities.get) != "function") {
    matched = "W.model.cities.get";
    return false;
  }
  if (typeof(W.model.countries) != "object") {
    matched = "W.model.countries";
    return false;
  }
  if (typeof(W.model.countries.get) != "function") {
    matched = "W.model.countries.get";
    return false;
  }
  if (typeof(W.model.actionManager) != "object") {
    matched = "W.model.actionManager";
    return false;
  }
  if (typeof(W.model.actionManager.add) != "function") {
    matched = "W.model.actionManager.add";
    return false;
  }
  if (typeof(W.Action) != "function") {
    matched = "W.Action";
    return false;
  }
  if (typeof(W.Action.UpdateObject) != "function") {
    matched = "W.Action.UpdateObject";
    return false;
  }
  if (typeof(W.Action.UpdateSegmentAddress) != "function") {
    matched = "W.Action.UpdateSegmentAddress";
    return false;
  }
  if (typeof(W.Action.DisconnectSegment) != "function") {
    matched = "W.Action.DisconnectSegment";
    return false;
  }
  if (typeof(W.Action.UpdateSegmentGeometry) != "function") {
    matched = "W.Action.UpdateSegmentGeometry";
    return false;
  }
  if (typeof(W.Action.AddSegment) != "function") {
    matched = "W.Action.AddSegment";
    return false;
  }
  if (typeof(W.Action.AddNode) != "function") {
    matched = "W.Action.AddNode";
    return false;
  }
  if (typeof(W.Action.ConnectSegment) != "function") {
    matched = "W.Action.ConnectSegment";
    return false;
  }
  if (typeof(W.Action.ModifyAllConnections) != "function") {
    matched = "W.Action.ModifyAllConnections";
    return false;
  }
  if (typeof(Waze.Feature) != "object") {
    matched = "Waze.Feature";
    return false;
  }
  if (typeof(Waze.Feature.Vector) != "function") {
    matched = "Waze.Feature.Vector";
    return false;
  }
  if (typeof(Waze.Feature.Vector.Segment) != "function") {
    matched = "Waze.Feature.Vector.Segment";
    return false;
  }
  if (typeof(W.model.nodes.objects) != "object") {
    matched = "W.model.nodes.objects";
    return false;
  }
  tstvar = Object.keys(W.model.nodes.objects);
  if (typeof(tstvar) != "object") {
    matched = "W.model.nodes.objects keys";
    return false;
  }
  return true;
}

function WME_JNF_RestoreSettings() {
  // restore saved setting
  if (storage) {
    console.log("WME-JNF: loading options");
    options = JSON.parse(storage.getItem('WME_JNF'));
    if (options == null) {
      console.log("no options");
      return;
    }
    if (WCENC) {
      WCENC.showAllArrows = options['showallarrows'];
      WCENC.showArrows = options['showarrows'];
      WCENC.toggleShowAllArrows();
      WCENC.toggleShowAllArrows();
    } else {
      console.log("no WCENC");
    }
    if (WCSAVE) {
      WCSAVE.controller.events.register("saveend", this, WME_JNF_SaveEnd);
      WME_JNF_PatchAndReload();
    } else {
      console.log("no WCSAVE");
    }
    if (options['fullscreen']) {
      if (options['fullscreen'] == "fullscreen" && document.body.className != "fullscreen") {
        console.log("going fullscreen");
        W.map.toggleFullscreen();
      }
    }
  }
//  W.model.events.unregister("mergeend", this, WME_JNF_RestoreSettings);
}

function WME_JNF_OnUnload() {
    if (storage) {
      console.log("WME-JNF: saving options");
      options = {};

      //    options['hotkey'] = getId('_cbJNF_Hotkey');
      Object.forEach(W.map.controls, function(k, v) {
        if (v.displayClass == "WazeControlEditNodeConnections") {
          options['showallarrows'] = v.showAllArrows;
          options['showarrows'] = v.showArrows;
        }
      });
      options['fullscreen'] = document.body.className;
      storage.setItem('WME_JNF', JSON.stringify(options));
    }
}

function WME_JNF_Hook() {
  console.log("WME-JNF: Hook");
  // make cameras visible at zoom 0 and load at zoom 1
  Waze.Config.cameras.minDisplayZoom = 0;
  W.model.cameras.minZoom = 0;
  
  // update pan amount so keyboard panning is useful
  W.map.DefaultPanInPixel = W.map.size.h / 4;

  $(window).on("beforeunload", WME_JNF_OnUnload);

  // hook 'q'
  Waze.accelerators.events.listeners.disallowAllConnections[0].func = function() {
    WME_JNF_DAT(this);
  }

  // hook 'w'
  Waze.accelerators.events.listeners.allowAllConnections[0].func = function() {
    if (typeof(allowAllConnections) == 'function') {
      allowAllConnections();
    } else {
      this.setAllConnections(true);
    }
    
    // refresh turn arrows
    WCENC.toggleShowAllArrows();
    WCENC.toggleShowAllArrows();
  }
//  W.model.events.unregister("mergeend", this, WME_JNF_Hook);
}

var init_tries = 0;

function WME_JNF_Init() {
  console.log("WME-JNF: " + WME_JNF_Version + " starting");
  
  try {
    uid = new Date;
    (storage = window.localStorage).setItem(uid, uid);
    fail = storage.getItem(uid) != uid;
    storage.removeItem(uid);
    fail && (storage = false);
  } catch(e) {}
  
  console.log("WME-JNF: Checking API");
  if (WME_JNF_CheckAPI()) {
    WME_JNF_RestoreSettings();
    WME_JNF_Hook();
  } else {
    console.log("WME-JNF: failed API check, exiting. " + matched);
    alert("WME Junction Node Fixer has failed to load due to API check: " + matched);
    WME_JNF_FixNode = undefined;
    return;
  }
}

$(document).ready(WME_JNF_Bootstrap);
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
manoeuvre wrote:Also noticed that it doesn't fix self-connecting roads.
This version is not appropriate for use in Canada, due to the modifications in the dead-end u-turn code.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
masvbr wrote:is there any link (I think on the 1st page would be great) to fresh version extension for FF? ... or am I just totaly blind?
bgodette seems to have been ignoring all the requests for this.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
james890526 wrote: That's because you should be using the Chrome webstore extension instead of the GM script
Theoretically, the Webstore edition is just a packaged UserScript...
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
Taco909 wrote:I have no way of opening the Webstore version of the script to verify if it is the same one that he posted.
I didn't do anything too magical.

I found wme-jnf.user.js in:
C:\Users\USERNAME\AppData\Local\Google\Chrome\User Data\Default\Extensions\dhnjmbmlldgfomcdmflifibpappdadcm\0.0.9.3_0

I'm not sure if the extensions subfolder (dhnjm...) is randomly named or if it is specific to all JNF installations.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
Yeah, I just tried it. It appears there's some substantial changes to the bootstrapping process between the two versions (0.8.2 and 0.9.3).

bgodette will have to decide to support FF/GM -- probably isn't too hard, but I don't understand the bootstrapping well enough to provide you with a patch.
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
Here's a version of 0.0.9.3 that seems to work on FF in GreaseMonkey for me.

I did some basic testing -- there's no error thrown to the user, it fixes RevConns and u-turns.

N.b. this is only modified from the original to have the bootstrapping code that works with FF. It will cause problems with the editing standards in Canada and the UK (i.e. it enables dead-end u-turns).

Can someone do some further testing and report back if there are any issues?

Code: Select all

// ==UserScript==
// @name                WME Junction Node Fixer
// @description         Creates a new editor hotkey to lock turns, fix reverse connectivity, and restore original restricted turns.
// @include             https://*.waze.com/editor/*
// @include             https://*.waze.com/*/editor/*
// @version             0.0.9.3
// @grant             none
// ==/UserScript==

if ('undefined' == typeof __RTLM_PAGE_SCOPE_RUN__) {
  (function page_scope_runner() {
    // If we're _not_ already running in the page, grab the full source
    // of this script.
    var my_src = "(" + page_scope_runner.caller.toString() + ")();";

    // Create a script node holding this script, plus a marker that lets us
    // know we are running in the page scope (not the Greasemonkey sandbox).
    // Note that we are intentionally *not* scope-wrapping here.
    var script = document.createElement('script');
    script.setAttribute("type", "text/javascript");
    script.textContent = "var __RTLM_PAGE_SCOPE_RUN__ = true;\n" + my_src;

    // Insert the script node into the page, so it will run, and immediately
    // remove it to clean up.  Use setTimeout to force execution "outside" of
    // the user script scope completely.
    setTimeout(function() {
      document.body.appendChild(script);
      document.body.removeChild(script);
    }, 1000);
  })();

  // Stop running, because we know Greasemonkey actually runs us in
  // an anonymous wrapper.
  return;
}

WME_JNF_Version = "v0.0.9.3";

function WME_JNF_Bootstrap()
{
  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! */
  setTimeout(WME_JNF_Init, 500);
}

var matched = false;
var WME_Version = undefined;

// Feature detect + local reference
var storage, fail, uid;
var options = {};

var WCENC = null;
var WCSAVE = null;
var WCCHAT = null;
var WCLU = null;
var WCUR = null;
var WCMP = null;

var UpdateObject, SplitSegments, UpdateSegmentAddress, DisconnectSegment, AddSegment, UpdateSegmentGeometry, ConnectSegment, AddNode, ModifyAllConnections, ModifyConnection, FeatureVectorSegment;

function WME_JNF_PatchAndReload() {
  var patch;
}

function WME_JNF_SaveEnd(b) {
  console.log("WME-JNF: Save %s", b.success ? "succeeded" : "failed");
  if (b.success) {
    WCSAVE.controller.reload();
  }
}


function WME_JNF_smn(s1, s2)
{
  var pmap = [0, 10, 11, 15, 14, 1, 13, 12, 8, 0, 3, 0, 0, 0, 0, 0, 2, 7, 5, 4, 6, 9];
  var mod = null;
  if (s1.isGeometryEditable() && !s2.isGeometryEditable()) {
    console.log("only s1 can be modified");
    mod = s1;
  } else if (!s1.isGeometryEditable() && s2.isGeometryEditable()) {
    console.log("only s2 can be modified");
    mod = s2;
  } else if (s1.isGeometryEditable() && s2.isGeometryEditable()) {
    /* pick one */
    if (pmap[s1.attributes.roadType] < pmap[s2.attributes.roadType]) {
      console.log("s1 lower type");
      mod = s1;
    } else if (pmap[s1.attributes.roadType] > pmap[s2.attributes.roadType]) {
      console.log("s2 lower type");
      mod = s2;
    } else {
      if (s1.attributes.length > s2.attributes.length) {
        console.log("s1 longer");
        mod = s1;
      } else if (s1.attributes.length < s2.attributes.length) {
        console.log("s2 longer");
        mod = s2;
      } else {
        if (s1.attributes.createdOn > s2.attributes.createdOn) {
          console.log("s1 newer");
          mod = s1;
        } else if (s1.attributes.createdOn < s2.attributes.createdOn) {
          console.log("s2 newer");
          mod = s2;
        } else {
          if (s1.getID() > s2.getID()) {
            console.log("s1 higher id");
            mod = s1;
          } else {
            console.log("s2 higher id");
            mod = s2;
          }
        }
      }
    }
  } else {
    console.log("cannot modify either.");
  }
  if (mod) {
    var point;
    if (mod.geometry.components.length > 2) {
      console.log("Splitting: " + mod.getID() + " on geo point " + (Math.ceil(mod.geometry.components.length / 2) - 1) + " of " + mod.geometry.components.length);
      point = mod.geometry.components[Math.ceil(mod.geometry.components.length / 2) - 1];
    } else {
      point = mod.getCenter();
      console.log("Splitting:", mod.getID(), "at center.");
    }
    W.model.actionManager.add(new SplitSegments(mod, {splitAtPoint: point}));
  }
}

function WME_JNF_CleanRBT(jct) {
  var roadTypes = { "street": 1,      "primary": 2,   "freeway": 3,   "ramp": 4,
                    "trail": 5,       "major": 6,     "minor": 7,     "dirt": 8,
                    "boardwalk": 10,  "stairway": 16, "private": 17,  "railroad": 18,
                    "runway": 19,     "parking": 20,  "service": 21};
  var typenames = { 1: "street",      2: "primary",   3: "freeway",   4: "ramp",
                    5: "trail",       6: "major",     7: "minor",     8: "dirt",
                    10: "boardwalk",  16: "stairway", 17: "private",  18: "railroad",
                    19: "runway",     20: "parking",  21: "service"};
  var prec = [4, 6, 7, 2, 1, 21, 17, 20, 8];
  if (jct.valid == true) {
    var types = {};
    var roadtype = false;
    var cities = {};
    var i = 0;
    var cityid = 0;
    var street = null;
    var city = null;
    var state = null;
    var country = null;
    var update = true;
    var street_updated = false;
    var type_updated = false;
    var nodes = {};
    
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      for (var i = 0; i < seg.geometry.components.length; i++)
        if (!onScreen(seg))
          update = false;
    });

    if (update == false)
      return;
    
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      if (seg.attributes.primaryStreetID) {
        street = W.model.streets.get(seg.attributes.primaryStreetID);
        city = W.model.cities.get(street.cityID);
        if (city) {
          if (!cities[street.cityID])
            cities[street.cityID] = 0;
          if (!city.isEmpty)
            cities[street.cityID] += 100;
        }
      }
      nodes[seg.attributes.toNodeID] = W.model.nodes.get(seg.attributes.toNodeID);
      nodes[seg.attributes.fromNodeID] = W.model.nodes.get(seg.attributes.fromNodeID);
    });
    Object.forEach(nodes, function(k, node) {
      node.attributes.segIDs.forEach(function(csegid) {
        var cseg = W.model.segments.get(csegid);
        if (!cseg.attributes.junctionID) {
          if (cseg.attributes.roadType != roadTypes["freeway"]) {
            if (!types[cseg.attributes.roadType])
              types[cseg.attributes.roadType] = 0;
            if (cseg.attributes.fwdDirection)
              types[cseg.attributes.roadType] += 1;
            if (cseg.attributes.revDirection)
              types[cseg.attributes.roadType] += 1;
            if (cseg.attributes.primaryStreetID) {
              street = W.model.streets.get(cseg.attributes.primaryStreetID);
              city = W.model.cities.get(street.cityID);
              if (city) {
                if (!cities[street.cityID])
                  cities[street.cityID] = 0;
                if (city.isEmpty) {
                  cities[street.cityID] += 1; 
                } else {
                  cities[street.cityID] += 2;
                }
              }
            }
          } 
        }
      });
    });
    i = 0;
    Object.forEach(cities, function(k, v) {
      if (i < v) {
        i = v;
        cityid = k;
      }
    });
    street = W.model.streets.getByAttributes({isEmpty: true, cityID: cityid}).first();
    city = W.model.cities.get(cityid);
    if (city)
      country = W.model.countries.get(city.countryID);
    state = null;
    if (city && city.stateID)
      state = W.model.states.get(city.stateID);
    var j;
    for (i = 0; i < prec.length && !roadtype; i++) {
      if (city.countryID == 234 && prec[i] == 4)
        continue;
      if (prec[i] in types) {
        if (types[prec[i]] > 3 || (types[prec[i]] && city.countryID == 234)) {
          roadtype = prec[i];
        } else {
          for (j = i+1; j < prec.length && !roadtype; j++) {
            if (types[prec[j]] > 1) {
              roadtype = prec[j];
            }
          }
        }
        if (!roadtype)
          roadtype = prec[i];
      }
    }
    jct.segIDs.forEach(function(segid) {
      var seg = W.model.segments.get(segid);
      if (seg.attributes.roadType != roadtype) {
        W.model.actionManager.add(new UpdateObject(seg, {roadType: roadtype}));
        if (!type_updated) {
          console.log("JNF_RBT: road type: " + roadtype + " " + typenames[roadtype]);
          type_updated = true;
        }
      }
      if (!seg.attributes.primaryStreetID || (street && seg.attributes.primaryStreetID != street.id)) {
        W.model.actionManager.add(new UpdateSegmentAddress(seg, {countryID: city.countryID, stateID: city.stateID, cityName: city.name, emptyStreet: true}));
        if (!street_updated) {
          if (state.name != "Other")
            console.log("JNF_RBT: " + city.name + ", " + state.name + ", " + country.name);
          else
            console.log("JNF_RBT: " + city.name + ", " + country.name);
          street = W.model.streets.getByAttributes({isEmpty: true, cityID: cityid}).first();
          console.log("JNF_RBT: street: %o ", street);
          street_updated = true;
        }
      }
    });
    Object.forEach(nodes, function(k, node) {
      WME_JNF_FixNode(node, false);
    });
  }
}

function WME_JNF_DAT(a) {
  if (!a.enabled)
    return;
  WME_JNF_FixNode(a.selectedFeature, true);
}

function onScreen(obj) {
  if (obj.geometry) {
    return(W.map.getExtent().intersectsBounds(obj.geometry.getBounds()));
  }
  return(false);
}

WME_JNF_FixNode = function(node, doJunctions) {
  if (!node)
    return;
  if (!node.type)
    return;
  if (node.type != "node")
    return;
  if (node.areConnectionsEditable() && onScreen(node)) {
    connections = {};
    junctions = {};
    
    for (var i = 0; i < node.attributes.segIDs.length; i++) {
      var seg = W.model.segments.get(node.attributes.segIDs[i]);
      if (seg) {
        if (seg.attributes.toNodeID == seg.attributes.fromNodeID) {
          if (seg.attributes.junctionID) {
            console.log("single node rb");
          } else {
            console.log("single node loop");
          }
          var seg1geo = seg.geometry.clone();
          var seg2geo = seg.geometry.clone();
          var seg3geo = seg.geometry.clone();
          var mod3 = seg.geometry.components.length % 3;
          for (var i = 0; i < seg.geometry.components.length / 3 - 1; i++) {
            seg1geo.components.pop();
            seg1geo.components.pop();
            seg2geo.components.pop();
            seg2geo.components.shift();
            seg3geo.components.shift();
            seg3geo.components.shift();
          }
          if (mod3 == 2) {
            seg1geo.components.pop();
            seg3geo.components.shift();
          }
          if (mod3 == 0) {
            seg1geo.components.pop();
            seg1geo.components.pop();
            seg3geo.components.shift();
            seg3geo.components.shift();
          }
					seg1geo.calculateBounds();
					seg2geo.calculateBounds();
					seg3geo.calculateBounds();
          var newseg1, newseg3, ns1ls, ns3ls;
          if (node.attributes.connections) {
            newseg1 = new FeatureVectorSegment(seg1geo);
            newseg3 = new FeatureVectorSegment(seg3geo);
          } else {
            newseg1 = new FeatureVectorSegment({geometry: seg1geo});
            newseg3 = new FeatureVectorSegment({geometry: seg3geo});
          }
          newseg1.copyAttributes(seg);
          newseg3.copyAttributes(seg);
          newseg1.attributes.junctionID = null;
          newseg3.attributes.junctionID = null;
          newseg1.attributes.fromNodeID = null;
          newseg3.attributes.fromNodeID = null;
          newseg1.attributes.toNodeID = null;
          newseg3.attributes.toNodeID = null;
          if (!node.attributes.connections) {
            //newseg1.geometry = seg1geo;
            //newseg3.geometry = seg3geo;
						if (seg.setID) {
							newseg1.setID(null);
							newseg3.setID(null);
						} else {
							newseg1.geometry = seg1geo;
							newseg3.geometry = seg3geo;
						}
          }
          var joinsegs = [];
          joinsegs.push(newseg1);
          joinsegs.push(seg);
          W.model.actionManager.add(new DisconnectSegment(seg, node));
          W.model.actionManager.add(new DisconnectSegment(seg, node));
          W.model.actionManager.add(new UpdateSegmentGeometry(seg, seg.geometry, seg2geo));

          W.model.actionManager.add(new AddSegment(newseg1));
          W.model.actionManager.add(new AddSegment(newseg3));

          W.model.actionManager.add(new ConnectSegment(node, newseg1));
          W.model.actionManager.add(new ConnectSegment(node, newseg3));

          W.model.actionManager.add(new AddNode(seg1geo.components.last(), joinsegs));
          joinsegs = []
          joinsegs.push(seg);
          joinsegs.push(newseg3);
          W.model.actionManager.add(new AddNode(seg3geo.components.first(), joinsegs));
          W.model.actionManager.add(new UpdateObject(newseg1, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new UpdateObject(seg, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new UpdateObject(newseg3, {fwdTurnsLocked: true, revTurnsLocked: true}));
          W.model.actionManager.add(new ModifyAllConnections(newseg1.getToNode(), true));
          W.model.actionManager.add(new ModifyAllConnections(newseg1.getFromNode(), true));
          W.model.actionManager.add(new ModifyAllConnections(seg.getToNode(), true));
          W.model.actionManager.add(new ModifyAllConnections(seg.getFromNode(), true));
          W.model.actionManager.add(new ModifyAllConnections(newseg3.getToNode(), true));
          W.model.actionManager.add(new ModifyAllConnections(newseg3.getFromNode(), true));
        }
        if (!seg.isDeleted()) {
          // store any roundabouts we see
          if (seg.attributes.junctionID) {
            junctions[seg.attributes.junctionID] = W.model.junctions.get(seg.attributes.junctionID);
          }

          // terminate unterminated dead-ends
          var segments = [];
          segments.push(seg);
          if (seg.attributes.toNodeID == null) {
            W.model.actionManager.add(new AddNode(seg.geometry.components.last(), segments));
          }
          if (seg.attributes.fromNodeID == null) {
            W.model.actionManager.add(new AddNode(seg.geometry.components.first(), segments));
          }

          var toNode = seg.getToNode();
          var fromNode = seg.getFromNode();
          if (toNode && fromNode && !toNode.isDeleted() && !fromNode.isDeleted()) {
            if (onScreen(toNode) && onScreen(fromNode)) {
              if ((seg.attributes.fwdDirection == false || seg.attributes.revDirection == false) && (toNode.attributes.segIDs.length < 2 || fromNode.attributes.segIDs.length < 2))
              {
                console.log("JNF: updating dead-end segment " + seg.getID() + " to two-way");
                W.model.actionManager.add(new UpdateObject(seg, {fwdDirection: true, revDirection: true}));
              }
              if (toNode.attributes.connections) {
                // old editor
                if (toNode.attributes.segIDs.length < 2 && !toNode.isTurnAllowed(seg, seg)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new ModifyConnection(seg.getID(), toNode, seg.getID(), true));
                  if (!seg.attributes.fwdTurnsLocked) {
                     W.model.actionManager.add(new UpdateObject(seg, {fwdTurnsLocked: true}));
                  }
                }
                if (fromNode.attributes.segIDs.length < 2 && !fromNode.isTurnAllowed(seg, seg)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new ModifyConnection(seg.getID(), fromNode, seg.getID(), true));
                  if (!seg.attributes.revTurnsLocked) {
                     W.model.actionManager.add(new UpdateObject(seg, {revTurnsLocked: true}));
                  }
                }
              } else {
                // beta editor
                if (toNode.attributes.segIDs.length < 2 && !seg.isTurnAllowed(seg, toNode)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new ModifyConnection(seg.getID(), toNode, seg.getID(), true));
                }
                if (fromNode.attributes.segIDs.length < 2 && !seg.isTurnAllowed(seg, fromNode)) {
                  console.log("Enabling dead-end u-turn at", toNode.getID(), "on", seg.getID());
                  W.model.actionManager.add(new ModifyConnection(seg.getID(), fromNode, seg.getID(), true));
                }
              }
            }
            if (node.attributes.segIDs.length > 1) {
              // disable u-turns
              var turnAllowed;
              if (node.attributes.connections) {
                // old editor
                turnAllowed = node.isTurnAllowed(seg, seg);
              } else {
                // beta editor
                turnAllowed = seg.isTurnAllowed(seg, node);
              }
              if (turnAllowed) {
                console.log("Disabling U-Turn at", node.getID(), "on", seg.getID());
                W.model.actionManager.add(new ModifyConnection(seg.getID(), node, seg.getID(), false));
              }
            }
          }
        }
      }
    }
    
    if (node.attributes.segIDs.length > 1) {
      var seg1, seg2;
      for (var i = 0; i < node.attributes.segIDs.length - 1; i++) {
        seg1 = W.model.segments.get(node.attributes.segIDs[i]);
        for (var j = i + 1; j < node.attributes.segIDs.length; j++) {
          seg2 = W.model.segments.get(node.attributes.segIDs[j]);
          if (seg1.isDeleted() == false && seg2.isDeleted() == false) {
            var fwd_t;
            var rev_t;
            var fwd_a;
            var rev_a;
            if (node.attributes.connections) {
              fwd_t = node.isTurnAllowed(seg1, seg2);
              rev_t = node.isTurnAllowed(seg2, seg1);
            } else {
              fwd_t = seg1.isTurnAllowed(seg2, node);
              rev_t = seg2.isTurnAllowed(seg1, node);
            }
            fwd_a = node.isTurnAllowedBySegDirections(seg1, seg2);
            rev_a = node.isTurnAllowedBySegDirections(seg2, seg1);
            if (fwd_t && !fwd_a) {
              console.log("Disabling RevCon at", node.getID(), "into", seg2.getID());
              W.model.actionManager.add(new ModifyConnection(seg1.getID(), node, seg2.getID(), false));
            }
            if (rev_t && !rev_a) {
              console.log("Disabling RevCon at", node.getID(), "into", seg1.getID());
              W.model.actionManager.add(new ModifyConnection(seg2.getID(), node, seg1.getID(), false));
            }
            if ((seg1.attributes.fromNodeID == seg2.attributes.fromNodeID && seg1.attributes.toNodeID == seg2.attributes.toNodeID) ||
                (seg1.attributes.fromNodeID == seg2.attributes.toNodeID && seg1.attributes.toNodeID == seg2.attributes.fromNodeID)) {
              console.log("sid:", seg1.getID(), "and sid:", seg2.getID(), "connected to same nodes:", seg1.attributes.fromNodeID, seg1.attributes.toNodeID);
              WME_JNF_smn(seg1, seg2);
            }
          }
        }
        if (!seg1.isDeleted() && !seg1.areTurnsLocked(node)) {
          var attr = seg1.getTurnsLockAttribute(node);
          var dict = {}
          dict[attr] = true;
          console.log("Locking Turns at", node.getID(), "on", seg1.getID());
          W.model.actionManager.add(new UpdateObject(seg1, dict));
        }
      }
      if (!seg2.isDeleted() && !seg2.areTurnsLocked(node)) {
        var attr = seg2.getTurnsLockAttribute(node);
        var dict = {}
        dict[attr] = true;
        console.log("Locking Turns at", node.getID(), "on", seg2.getID());
        W.model.actionManager.add(new UpdateObject(seg2, dict));
      }
    }

    if (doJunctions) {
      // clean up roundabouts
      Object.forEach(junctions, function(i, j) {
        WME_JNF_CleanRBT(j);
      });
    }
    
    // refresh turn arrows
    WCENC.toggleShowAllArrows();
    WCENC.toggleShowAllArrows();
  }
}

function WME_JNF_CheckAPI() {
  if (typeof(Waze) != "object") {
    matched = "Waze";
    return false;
  }
  if (typeof(W.model) != "object") {
    matched = "W.model";
    return false;
  }
  if (typeof(W.map) != "object") {
    matched = "W.map";
    return false;
  }
  if (typeof(W.map.controls) != "object") {
    matched = "W.map.controls";
    return false;
  }
  if (typeof(W.map.controls[0]) != "object") {
    matched = "W.map.controls[0]";
    return false;
  }
  if (typeof(W.map.controls[0].displayClass) != "string") {
    matched = "W.map.controls[0].displayClass";
    return false;
  }
  Object.forEach(W.map.controls, function(k, v) {
    if (v.displayClass == "WazeControlEditNodeConnections") {
      WCENC = v;
    }
    if (v.displayClass == "WazeControlSave") {
      WCSAVE = v;
    }
    if (v.displayClass == "WazeControlChat") {
      WCCHAT = v;
    }
    if (v.displayClass == "WazeControlMapProblems") {
      WCMP = v;
    }
    if (v.displayClass == "WazeControlUpdateRequests") {
      WCUR = v;
    }
    if (v.displayClass == "WazeControlLiveUsers") {
      WCLU = v;
    }
  });
  if (typeof(WCENC) != "object") {
    matched = "WCENC";
    return false;
  }
  if (typeof(WCSAVE) != "object") {
    matched = "WCSAVE";
    return false;
  }
  if (typeof(WCCHAT) != "object") {
    matched = "WCCHAT";
    return false;
  }
  if (typeof(WCMP) != "object") {
    matched = "WCMP";
    return false;
  }
  if (typeof(WCUR) != "object") {
    matched = "WCUR";
    return false;
  }
  if (typeof(WCLU) != "object") {
    matched = "WCLU";
    return false;
  }
  if (typeof(Waze.Config) != "object") {
    matched = "Waze.Config";
    return false;
  }
  if (typeof(Waze.Config.cameras) != "object") {
    matched = "Waze.Config.cameras";
    return false;
  }
  if (typeof(Waze.Config.cameras.minDisplayZoom) != "number") {
    matched = "Waze.Config.cameras.minDisplayZoom";
    return false;
  }
  if (typeof(W.model.cameras) != "object") {
    matched = "W.model.cameras";
    return false;
  }
  if (typeof(W.model.cameras.minZoom) != "number") {
    matched = "W.model.cameras.minZoom";
    return false;
  }
  if (typeof(W.map.toggleFullscreen) != "function") {
    matched = "W.map.toggleFullscreen";
    return false;
  }
  if (typeof(WCENC.showAllArrows) != "boolean") {
    matched = "WCENC.showAllArrows";
    return false;
  }
  if (typeof(WCENC.showArrows) != "boolean") {
    matched = "WCENC.showArrows";
    return false;
  }
  if (typeof(WCENC.toggleShowAllArrows) != "function") {
    matched = "WCENC.toggleShowAllArrows";
    return false;
  }
  if (typeof(WCSAVE.controller) != "object") {
    matched = "WCSAVE.controller";
    return false;
  }
  if (typeof(WCSAVE.controller.events) != "object") {
    matched = "WCSAVE.controller.events";
    return false;
  }
  if (typeof(WCSAVE.controller.events.register) != "function") {
    matched = "WCSAVE.controller.events.register";
    return false;
  }
  if (typeof(W.map.DefaultPanInPixel) != "number") {
    matched = "W.map.DefaultPanInPixel";
    return false;
  }
  if (typeof(Waze.accelerators) != "object") {
    if (typeof(Waze.Accelerators) != "object") {
      matched = "Waze.accelerators";
      return false;
    } else {
      Waze.accelerators = Waze.Accelerators;
    }
  }
  if (typeof(Waze.accelerators.events) != "object") {
    matched = "Waze.accelerators.events";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners) != "object") {
    matched = "Waze.accelerators.events.listeners";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections) != "object") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections[0]) != "object") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections[0]";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.disallowAllConnections[0].func) != "function") {
    matched = "Waze.accelerators.events.listeners.disallowAllConnections[0].func";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections) != "object") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections[0]) != "object") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections[0]";
    return false;
  }
  if (typeof(Waze.accelerators.events.listeners.allowAllConnections[0].func) != "function") {
    matched = "Waze.accelerators.events.listeners.allowAllConnections[0].func";
    return false;
  }
  if (typeof(W.map.getExtent) != "function") {
    matched = "W.map.getExtent";
    return false;
  }
  var tstvar = W.map.getExtent();
  if (typeof(tstvar) != "object") {
    matched = "W.map.getExtent()";
    return false;
  }
  if (typeof(tstvar.toGeometry) != "function") {
    matched = "W.map.getExtent().toGeometry";
    return false;
  }
  var tstvar = tstvar.toGeometry();
  if (typeof(tstvar) != "object") {
    matched = "W.map.getExtent().toGeometry() == object";
    return false;
  }
  if (typeof(tstvar.containsPoint) != "function") {
    matched = "W.map.getExtent().toGeometry().containsPoint";
    return false;
  }
  if (typeof(W.model.segments) != "object") {
    matched = "W.model.segments";
    return false;
  }
  if (typeof(W.model.segments.get) != "function") {
    matched = "W.model.segments.get";
    return false;
  }
  if (typeof(W.model.nodes) != "object") {
    matched = "W.model.nodes";
    return false;
  }
  if (typeof(W.model.nodes.get) != "function") {
    matched = "W.model.nodes.get";
    return false;
  }
  if (typeof(W.model.junctions) != "object") {
    matched = "W.model.junctions";
    return false;
  }
  if (typeof(W.model.junctions.get) != "function") {
    matched = "W.model.junctions.get";
    return false;
  }
  if (typeof(W.model.streets) != "object") {
    matched = "W.model.streets";
    return false;
  }
  if (typeof(W.model.streets.get) != "function") {
    matched = "W.model.streets.get";
    return false;
  }
  if (typeof(W.model.streets.getByAttributes) != "function") {
    matched = "W.model.streets.getByAttributes";
    return false;
  }
  if (typeof(W.model.cities) != "object") {
    matched = "W.model.cities";
    return false;
  }
  if (typeof(W.model.cities.get) != "function") {
    matched = "W.model.cities.get";
    return false;
  }
  if (typeof(W.model.countries) != "object") {
    matched = "W.model.countries";
    return false;
  }
  if (typeof(W.model.countries.get) != "function") {
    matched = "W.model.countries.get";
    return false;
  }
  if (typeof(W.model.actionManager) != "object") {
    matched = "W.model.actionManager";
    return false;
  }
  if (typeof(W.model.actionManager.add) != "function") {
    matched = "W.model.actionManager.add";
    return false;
  }
  if (typeof(UpdateObject) != "function") {
    matched = "UpdateObject";
    return false;
  }
  if (typeof(UpdateSegmentAddress) != "function") {
    matched = "UpdateSegmentAddress";
    return false;
  }
  if (typeof(DisconnectSegment) != "function") {
    matched = "DisconnectSegment";
    return false;
  }
  if (typeof(UpdateSegmentGeometry) != "function") {
    matched = "UpdateSegmentGeometry";
    return false;
  }
  if (typeof(AddSegment) != "function") {
    matched = "AddSegment";
    return false;
  }
  if (typeof(AddNode) != "function") {
    matched = "AddNode";
    return false;
  }
  if (typeof(ConnectSegment) != "function") {
    matched = "ConnectSegment";
    return false;
  }
  if (typeof(ModifyAllConnections) != "function") {
    matched = "ModifyAllConnections";
    return false;
  }
  if (typeof(FeatureVectorSegment) != "function") {
    matched = "FeatureVectorSegment";
    return false;
  }
  if (typeof(W.model.nodes.objects) != "object") {
    matched = "W.model.nodes.objects";
    return false;
  }
  tstvar = Object.keys(W.model.nodes.objects);
  if (typeof(tstvar) != "object") {
    matched = "W.model.nodes.objects keys";
    return false;
  }
  return true;
}

function WME_JNF_RestoreSettings() {
  // restore saved setting
  if (storage) {
    console.log("WME-JNF: loading options");
    options = JSON.parse(storage.getItem('WME_JNF'));
    if (options == null) {
      console.log("no options");
      return;
    }
    if (WCENC) {
      WCENC.showAllArrows = options['showallarrows'];
      WCENC.showArrows = options['showarrows'];
      WCENC.toggleShowAllArrows();
      WCENC.toggleShowAllArrows();
    } else {
      console.log("no WCENC");
    }
    if (WCSAVE) {
      WCSAVE.controller.events.register("saveend", this, WME_JNF_SaveEnd);
      WME_JNF_PatchAndReload();
    } else {
      console.log("no WCSAVE");
    }
    if (options['fullscreen']) {
      if (options['fullscreen'] == "fullscreen" && document.body.className != "fullscreen") {
        console.log("going fullscreen");
        W.map.toggleFullscreen();
      }
    }
  }
//  W.model.events.unregister("mergeend", this, WME_JNF_RestoreSettings);
}

function WME_JNF_OnUnload() {
    if (storage) {
      console.log("WME-JNF: saving options");
      options = {};

      //    options['hotkey'] = getId('_cbJNF_Hotkey');
      Object.forEach(W.map.controls, function(k, v) {
        if (v.displayClass == "WazeControlEditNodeConnections") {
          options['showallarrows'] = v.showAllArrows;
          options['showarrows'] = v.showArrows;
        }
      });
      options['fullscreen'] = document.body.className;
      storage.setItem('WME_JNF', JSON.stringify(options));
    }
}

function WME_JNF_Hook() {
  console.log("WME-JNF: Hook");
  // make cameras visible at zoom 0 and load at zoom 1
  Waze.Config.cameras.minDisplayZoom = 0;
  W.model.cameras.minZoom = 0;
  
  // update pan amount so keyboard panning is useful
  W.map.DefaultPanInPixel = W.map.size.h / 4;

  $(window).on("beforeunload", WME_JNF_OnUnload);

  // hook 'q'
  Waze.accelerators.events.listeners.disallowAllConnections[0].func = function() {
    WME_JNF_DAT(this);
  }

  // hook 'w'
  Waze.accelerators.events.listeners.allowAllConnections[0].func = function() {
    if (typeof(allowAllConnections) == 'function') {
      allowAllConnections();
    } else {
      this.setAllConnections(true);
    }
    
    // refresh turn arrows
    WCENC.toggleShowAllArrows();
    WCENC.toggleShowAllArrows();
  }
//  W.model.events.unregister("mergeend", this, WME_JNF_Hook);
}

var init_tries = 0;

function WME_JNF_Init() {
  console.log("WME-JNF: " + WME_JNF_Version + " starting");
  
  try {
    uid = new Date;
    (storage = window.localStorage).setItem(uid, uid);
    fail = storage.getItem(uid) != uid;
    storage.removeItem(uid);
    fail && (storage = false);
  } catch(e) {}
  
  console.log("WME-JNF: Checking API");
	if (typeof(require) !== "undefined") {
		UpdateObject = require("Waze/Action/UpdateObject");
		SplitSegments = require("Waze/Action/SplitSegments");
		UpdateSegmentAddress = require("Waze/Action/UpdateSegmentAddress");
		DisconnectSegment = require("Waze/Action/DisconnectSegment");
		AddSegment = require("Waze/Action/AddSegment");
		UpdateSegmentGeometry = require("Waze/Action/UpdateSegmentGeometry");
		ConnectSegment = require("Waze/Action/ConnectSegment");
		AddNode = require("Waze/Action/AddNode");
		ModifyAllConnections = require("Waze/Action/ModifyAllConnections");
		ModifyConnection = require("Waze/Action/ModifyConnection");
		FeatureVectorSegment = require("Waze/Feature/Vector/Segment");
	} else {
		UpdateObject = W.Action.UpdateObject;
		SplitSegments = W.Action.SplitSegments;
		UpdateSegmentAddress = W.Action.UpdateSegmentAddress;
		DisconnectSegment = W.Action.DisconnectSegment;
		AddSegment = W.Action.AddSegment;
		UpdateSegmentGeometry = W.Action.UpdateSegmentGeometry;
		ConnectSegment = W.Action.ConnectSegment;
		AddNode = W.Action.AddNode;
		ModifyAllConnections = W.Action.ModifyAllConnections;
		ModifyConnection = W.Action.ModifyConnection;
		FeatureVectorSegment = W.Feature.Vector.Segment;
	}
  if (WME_JNF_CheckAPI()) {
    WME_JNF_RestoreSettings();
    WME_JNF_Hook();
  } else {
    console.log("WME-JNF: failed API check, exiting. " + matched);
    alert("WME Junction Node Fixer has failed to load due to API check: " + matched);
    WME_JNF_FixNode = undefined;
    return;
  }
}

$(document).ready(WME_JNF_Bootstrap);
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message

Post by doctorkb
Keep in mind that JNF (to my recollection) never showed them... That's always been highlights or toolbox
doctorkb
Posts: 4385
Answers: 4
Has thanked: 433 times
Been thanked: 1464 times
Send a message