// ==UserScript==
// @name WME Reverse Nodes
// @name:fr WME Reverse Nodes
// @description Serves to reverse the nodes A and B of a segment, in order to better manage restrictions on multiples segments.
// @description:fr Sert à inverser les nodes A et B d'un segment, dans le but de mieux gerer les restrictions sur segments multiples.
// @match https://beta.waze.com/*editor*
// @match https://www.waze.com/*editor*
// @exclude https://www.waze.com/*user/*editor/*
// @namespace WME_Reverse_Nodes
// @version 2.09
// @grant unsafeWindow
// ==/UserScript==
/* global $ */
/* global require */
/* global W */
var WRNAB_version = "2.09";
var debug = true;
var wazeOBJ = {};
var w;
var newversion = 0;
/* bootstrap */
function WRNAB_bootstrap(){
if (typeof(unsafeWindow) === "undefined"){
unsafeWindow = ( function () {
var dummyElem = document.createElement('p');
dummyElem.setAttribute('onclick', 'return window;');
return dummyElem.onclick();
}) ();
}
/* begin running the code! */
log("Start");
if (W.version.substr(0,6) > 'v2.179') { newversion = 1 }
setTimeout(initializeWazeObjects, 1000);
}
//========== Helper ==============================//
function getElementsByClassName(classname, node) {
if(!node) node = document.getElementsByTagName("body")[0];
var a = [];
var re = new RegExp('\\b' + classname + '\\b');
var els = node.getElementsByTagName("*");
for (var i=0,j=els.length; i<j; i++){
if (re.test(els[i].className)) a.push(els[i]);
}
return a;
}
function getId(node) {
return document.getElementById(node);
}
function log(msg, obj){
if (obj==null){
console.log("WME Reverse Nodes AB v" + WRNAB_version + " - " + msg);
}
else if(debug){
console.debug("WME Reverse Nodes AB v" + WRNAB_version + " - " + msg + " " ,obj);
}
}
function cloneObj(obj){
var copy = JSON.parse(JSON.stringify(obj));
return copy;
}
function clone(rest){
return[].concat(this)
}
function withReverseDirection(rest){
if(this.isBidi())return this;
var e=this.isForward()?w.REV:w.FWD;
return this.with({direction:e})
}
function getByID(obj, id){
if (typeof(obj.getObjectById) == "function"){
return obj.getObjectById(id);
}else if (typeof(obj.getObjectById) == "undefined"){
return obj.get(id);
}
}
//========== /Helper ==============================//
function initializeWazeObjects(){
log("init");
var objectToCheck = [
{o: "W", s: "waze"},
{o: "W.model", s: "wazeModel"},
{o: "W.map", s: "wazeMap"},
{o: "W.loginManager", s: "WazeLoginManager"},
{o: "W.selectionManager", s: "WazeSelectionManager"},
{o: "Waze/Action/UpdateObject", s: "WazeActionUpdateObject"},
{o: "Waze/Action/UpdateSegmentGeometry", s: "WazeUpdateSegmentGeometry"},
{o: "Waze/Action/ConnectSegment", s: "WazeActionConnectSegment"},
{o: "Waze/Action/DisconnectSegment", s: "WazeActionDisconnectSegment"},
{o: "Waze/Action/MultiAction", s: "WazeActionMultiAction"},
{o: "Waze/Model/Graph/Actions/SetTurn", s: "WazeModelGraphActionsSetTurn"},
//{o: "Waze/Model/Graph/Turn", s: "WazeModelGraphTurn"},
{o: "Waze/Model/Graph/TurnData", s: "WazeModelGraphTurnData"},
//{o: "Waze/Model/Graph/TurnGraph", s: "WazeModelGraphTurnGraph"},
//{o: "Waze/Model/Graph/Vertex", s: "WazeModelGraphVertex"},
{o: "W.loginManager.user", s: "me"},
{o: "localStorage", s: null}
];
if (newversion) {
objectToCheck.push({o: "W.loginManager.user.attributes.rank", s: "ul"})
objectToCheck.push({o: "W.loginManager.user.attributes.isAreaManager", s: "uam"})
} else {
objectToCheck.push({o: "W.loginManager.user.rank", s: "ul"})
objectToCheck.push({o: "W.loginManager.user.isAreaManager", s: "uam"})
}
for (var i=0; i<objectToCheck.length; i++){
if (objectToCheck[i].o.indexOf("/") != -1) {
if (objectToCheck[i].s != null) wazeOBJ[objectToCheck[i].s] = require(objectToCheck[i].o);
} else {
var path = objectToCheck[i].o.split(".");
var object = unsafeWindow;
for (var j = 0; j < path.length; j++) {
object = object[path[j]];
if (typeof object == "undefined" || object == null) {
window.setTimeout(initializeWazeObjects, 1000);
return;
}else{ if (objectToCheck[i].s != null) wazeOBJ[objectToCheck[i].s] = object;}
}
}
}
log("wazeOBJ :",wazeOBJ);
initializeWazeUI();
}
function initializeWazeUI(){
var userInfo = getId('user-info');
if (userInfo==null)
{
window.setTimeout(initializeWazeUI, 500);
return;
}
var navTabs=userInfo.getElementsByTagName('ul');
if (navTabs.length==0)
{
window.setTimeout(initializeWazeUI, 500);
return;
}
if (typeof(navTabs[0])==='undefined')
{
window.setTimeout(initializeWazeUI, 500);
return;
}
var tabContents=userInfo.getElementsByTagName('div');
if (tabContents.length==0)
{
window.setTimeout(initializeWazeUI, 500);
return;
}
if (typeof(tabContents[0])==='undefined')
{
window.setTimeout(initializeWazeUI, 500);
return;
}
WRNAB_Init();
}
function WRNAB_newSelectionAvailable(){
//log('wazeOBJ.WazeSelectionManager',wazeOBJ.WazeSelectionManager);
if (wazeOBJ.WazeSelectionManager.getSelectedFeatures() !== undefined){
var selection = wazeOBJ.WazeSelectionManager.getSelectedFeatures();
}
if (wazeOBJ.WazeSelectionManager.selectedItems !== undefined){
selection = wazeOBJ.WazeSelectionManager.selectedItems;
}
log('selection', selection);
if (selection === undefined || selection.length !=1 ){
return;
}
var selectedObject = W.version.substr(1,3) > '2.1' ? selection[0]._wmeObject : selection[0].attributes.wazeFeature._wmeObject;
if (selectedObject.type!="segment"){
return;
}
log('wazeOBJ.uam', wazeOBJ.uam);
if (!wazeOBJ.uam) return;
var editPanel=getId('edit-panel');
log(editPanel)
if (editPanel.firstElementChild.style.display=='none'){
window.setTimeout(WRNAB_newSelectionAvailable, 100);
}
// ok: 1 selected item and pannel is shown
append_WRNAB_Controle();
}
function append_WRNAB_Controle() { // wait for 'sidebar'
var WRNAB_Controle1 = create_WRNAB_Controle
if (document.getElementById('segment-edit-general')!=null) {
// if (!document.getElementById('WRNAB_Controle')) {
$("#segment-edit-general").append(WRNAB_Controle1);
// }
}
else {
setTimeout (function () {append_WRNAB_Controle();}, 1001);
}
}
function create_WRNAB_Controle () {
var WRNAB_Controle = $ ('<div id="WRNAB_Controle" style="border: none;"/>');
var btn1 = $('<button style="display: block; margin: auto; background-color: #0099FF; color: white; border: none; border-radius: 40px; box-sizing: border-box; line-height: 20px;">Reverse Nodes A/B</button>');
btn1.click (invertNodeAB);
var cnt1 = $('<section id="WRNAB_cnt1" style="margin:2px; display:inline;"/>'); cnt1.append(btn1);
WRNAB_Controle.append(cnt1);
return WRNAB_Controle;
}
function WRNAB_Init(){
wazeOBJ.WazeSelectionManager.events.register("selectionchanged", null, WRNAB_newSelectionAvailable);
log('init done.');
}
function onScreen(obj){
if (obj.geometry)
{
return(wazeOBJ.wazeMap.getExtent().intersectsBounds(obj.geometry.getBounds()));
}
return false;
}
function isSegmentEditable(seg){
var rep = true;
if (seg==null) return false;
//if (wazeOBJ.Waze.loginManager.user.isCountryManager() ) rep = true;
if (!wazeOBJ.uam) rep = false;
var ndA = getByID(wazeOBJ.wazeModel.nodes,seg.attributes.fromNodeID);
var ndB = getByID(wazeOBJ.wazeModel.nodes,seg.attributes.toNodeID);
if ((seg.attributes.permissions == 0) || (seg.attributes.hasClosures)){
rep = false;
}
/* if ((ndA.attributes.permissions == 0) || (seg.attributes.hasClosures)){
rep = false;
}
if ((ndB.attributes.permissions == 0) || (seg.attributes.hasClosures)){
rep = false;
}
*/
return rep;
}
function invertNodeAB(){
if (wazeOBJ.WazeSelectionManager.getSelectedFeatures() !== undefined){
var selection = wazeOBJ.WazeSelectionManager.getSelectedFeatures();
}
if (wazeOBJ.WazeSelectionManager.selectedItems !== undefined){
selection = wazeOBJ.WazeSelectionManager.selectedItems;
}
log('selection', selection);
if (selection === undefined || selection.length !=1 ){
return;
}
var seg = getByID(wazeOBJ.wazeModel.segments,W.version.substr(1,3) > '2.1' ? selection[0]._wmeObject.attributes.id : selection[0].attributes.wazeFeature._wmeObject.attributes.id);
if (seg.type!="segment"){
return;
}
var attr = seg.attributes;
log("seg",seg);
log("attr",attr);
var sid = attr.id;
// if unnamed, don't touch
if (attr.primaryStreetID==null){
log("Unnamed segment. Do not put my name on it!");
return;
}
// if locked, don't touch
/*if ((attr.lockRank==null && attr.rank>usrRank) ||
(attr.lockRank!=null && attr.lockRank>usrRank)){
log("locked: " + attr.rank + " " + attr.lockRank);
continue;
}
*/
var ndA = getByID(wazeOBJ.wazeModel.nodes,attr.fromNodeID);
var ndB = getByID(wazeOBJ.wazeModel.nodes,attr.toNodeID);
log("ndA", ndA);
log("ndB", ndB);
// verification de pbs sur nodes (unterminated roads par ex)
// check if bad node (unterminated roads or over...)
if (!ndA || !ndB){
log("Bad nodes: A=" + ndA + " or B=" + ndB);
return;
}
// On verifie que le segment est affiche a l'ecran
if (onScreen(ndA) && onScreen(ndB)){
log("IN Screen!");
}
else{
log("OUT of Screen");
return;
}
// On verifie que le segment est editable
console.log(seg)
if (!isSegmentEditable(seg)){
log("Not editable!");
return;
}
// Memorisation des parametre du segment pour les reporter apres changement de sens
// Storing segment parameters for restore them after changing direction
var newAttr={};
//var fromConnections = attr.toConnections.clone();
//var toConnections = attr.fromConnections.clone();
newAttr.fwdDirection = attr.revDirection;
newAttr.revDirection = attr.fwdDirection;
newAttr.fwdTurnsLocked = attr.revTurnsLocked;
newAttr.revTurnsLocked = attr.fwdTurnsLocked;
newAttr.fwdMaxSpeed = attr.revMaxSpeed;
newAttr.revMaxSpeed = attr.fwdMaxSpeed;
newAttr.fwdMaxSpeedUnverified = attr.revMaxSpeedUnverified;
newAttr.revMaxSpeedUnverified = attr.fwdMaxSpeedUnverified;
newAttr.fwdLaneCount = attr.revLaneCount;
newAttr.revLaneCount = attr.fwdLaneCount;
newAttr.restrictions = [];
for (var i=0; i<attr.restrictions.length; i++){
newAttr.restrictions[i] = attr.restrictions[i].withReverseDirection();
}
log("newAttr", newAttr);
/*----------------------------------------------------------------------------
New editor:
f = forward direction (A>B)
r = reverse direction (B>A)
W.model.turnGraph._adjacencyList["280125026r"]
--> {295413973f: {instructionOpcode: null,
restrictions:Array[0],
state:1 }
W.model.turnGraph.getTurn(W.model.segments.objects["280125026"],W.model.segments.objects["280125024"])
--> {fromVertex, toVertex , turnData}
turn = W.model.getTurnGraph().getTurnThroughNode(W.model.nodes.get("232242442"),W.model.segments.get("280125026"),W.model.segments.get("280125024"))
= W.model.turnGraph.getTurn( turn.getFromVertex(), turn.getToVertex())
--> {fromVertex, toVertex , turnData}
turn.getTurnData().getInstructionOpcode()
W.model.getRoadGraph().getVertexNodeID(W.model.getTurnGraph().getTurnThroughNode(W.model.nodes.get("232242442"),W.model.segments.get("280125026"),W.model.segments.get("280125024")).getFromVertex())
e {fromVertex: e, toVertex: e, turnData: e}
fromVertex:e
direction:true --> A>B
segmentID:69264747
toVertex:e
direction:false --> B>A
segmentID:69264745
turnData:e
instructionOpcode:null
restrictions:Array[0]
state:2
-------------------------------------
CLASS_NAME: "Waze.Action.SetTurn" --> require("Waze/Model/Graph/Actions/SetTurn");
-----------------------------------------------------------------------------*/
var sconA={};
var sconB={};
var fromConnections = {};
var toConnections = {};
// pour les segments connecte à la node A
for (var s=0; s<ndA.attributes.segIDs.length; s++){
var scon=ndA.attributes.segIDs[s];
// On memorise les directions entrantes
if (scon != attr.id){
if (debug){
log("sconA; mem "+scon+" --> ndA: "+ndA.attributes.id+" --> " + attr.id);
log("turn",wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndA, getByID(wazeOBJ.wazeModel.segments,scon), getByID(wazeOBJ.wazeModel.segments,attr.id)));
}
sconA[scon]= wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndA, getByID(wazeOBJ.wazeModel.segments,scon),getByID(wazeOBJ.wazeModel.segments,attr.id));
sconA[scon].toVertex.direction = sconA[scon].toVertex.direction == "fwd" ? "rev" : "fwd"
}
// On memorise les directions sortantes
if (debug){
log("toConnections; mem "+attr.id+" --> ndA: "+ndA.attributes.id+" --> " + scon);
log("turn",wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndA,getByID(wazeOBJ.wazeModel.segments,attr.id),getByID(wazeOBJ.wazeModel.segments,scon)));
}
toConnections[scon] = wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndA,getByID(wazeOBJ.wazeModel.segments,attr.id),getByID(wazeOBJ.wazeModel.segments,scon));
toConnections[scon].fromVertex.direction = toConnections[scon].fromVertex.direction == "fwd" ? "rev" : "fwd"
if (scon == attr.id){ //u-turn
toConnections[scon].toVertex.direction = toConnections[scon].toVertex.direction == "fwd" ? "rev" : "fwd"
}
}
// pour les segments connecte à la node B
for (s=0; s<ndB.attributes.segIDs.length; s++){
scon=ndB.attributes.segIDs[s];
// On memorise les directions entrantes
if (scon != attr.id){
if (debug){
log("sconB; mem "+scon+" --> ndB: "+ndB.attributes.id+" --> " + attr.id);
log("turn",wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndB,getByID(wazeOBJ.wazeModel.segments,scon),getByID(wazeOBJ.wazeModel.segments,attr.id)));
}
sconB[scon]= wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndB,getByID(wazeOBJ.wazeModel.segments,scon),getByID(wazeOBJ.wazeModel.segments,attr.id));
sconB[scon].toVertex.direction = sconB[scon].toVertex.direction == "fwd" ? "rev" : "fwd"
}
// On memorise les directions sortantes
if (debug){
log("fromConnections; mem "+attr.id+" --> ndB: "+ndB.attributes.id+" --> " + scon);
log("turn",wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndB,getByID(wazeOBJ.wazeModel.segments,attr.id),getByID(wazeOBJ.wazeModel.segments,scon)));
}
fromConnections[scon] = wazeOBJ.wazeModel.getTurnGraph().getTurnThroughNode(ndB,getByID(wazeOBJ.wazeModel.segments,attr.id),getByID(wazeOBJ.wazeModel.segments,scon));
fromConnections[scon].fromVertex.direction = fromConnections[scon].fromVertex.direction == "fwd" ? "rev" : "fwd"
if (scon == attr.id){ //u-turn
fromConnections[scon].toVertex.direction = fromConnections[scon].toVertex.direction == "fwd" ? "rev" : "fwd"
}
}
//log("wazeOBJ.wazeModel.getTurnGraph()._adjacencyList["+attr.id+"f]", wazeOBJ.wazeModel.getTurnGraph()._adjacencyList[attr.id+"f"]);
//log("wazeOBJ.wazeModel.getTurnGraph()._adjacencyList["+attr.id+"r]", wazeOBJ.wazeModel.getTurnGraph()._adjacencyList[attr.id+"r"]);
log("sconA",sconA);
log("sconB",sconB);
log("fromConnections", fromConnections);
log("toConnections", toConnections);
//on inverse la geometrie du segment
var geo = { ... seg.getGeometry() };
geo.coordinates.reverse();
// controle de position
var nbPoints = geo.coordinates.length-1;
if (!(JSON.stringify(geo.coordinates[0]) == JSON.stringify(ndB.getGeometry().coordinates))){
if (debug) log("point 0 et dif de node A");
var delta = {x:0, y:0};
delta.x =ndB.attributes.geoJSONGeometry.coordinates[0] - geo.coordinates[0][0];
delta.y = ndB.attributes.geoJSONGeometry.coordinates[1] - geo.coordinates[0][1];
geo.coordinates[0][0] += delta.x;
geo.coordinates[0][1] += delta.y;
}
if (!(JSON.stringify(geo.coordinates[nbPoints])) == (JSON.stringify(ndA.getOLGeometry().coordinates))){
if (debug) log("point "+nbPoints+ " est dif de node B");
delta = {x:0, y:0};
delta.x = ndA.attributes.geoJSONGeometry.coordinates[0] - geo.coordinates[nbPoints][0];
delta.y = ndA.attributes.geoJSONGeometry.coordinates[1] - geo.coordinates[nbPoints][1];
geo.coordinates[nbPoints][0] += delta.x;
geo.coordinates[nbPoints][1] += delta.y;
}
// On deconnecte le segment
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeActionMultiAction([new wazeOBJ.WazeActionDisconnectSegment(seg, ndA),new wazeOBJ.WazeActionDisconnectSegment(seg, ndB)]));
// maj de la geo du seg
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeUpdateSegmentGeometry (seg,seg.getGeometry(),geo));
//on replace les parametre inverse
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeActionUpdateObject(seg, newAttr));
// On reconnecte le segment
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeActionMultiAction([new wazeOBJ.WazeActionConnectSegment(ndB, seg),new wazeOBJ.WazeActionConnectSegment(ndA, seg)]));
//on replace les autorisations sortantes
var actions = [];
for (sid in fromConnections){
var segId = fromConnections[sid].toVertex.segmentID;
log("attr.id: " + attr.id + " --> NodeA: " + ndB.attributes.id + " --> segId: " + segId + " ; state = "+fromConnections[sid].turnData.state);
if (debug) log("fromConnections["+sid+"] = ", fromConnections[sid]);
switch (fromConnections[sid].turnData.state){
case 0 :
case 1 :
//var turn = new wazeOBJ.WazeModelGraphTurnData(wazeOBJ.WazeModelGraphTurnData.State.ALLOWED);
//var turn = new wazeOBJ.WazeModelGraphTurnData.createAllowed;
var turn = new wazeOBJ.WazeModelGraphTurnData;
turn = turn.withState(fromConnections[sid].turnData.state);
turn = turn.withRestrictions(fromConnections[sid].turnData.restrictions);
turn = turn.withInstructionOpcode(fromConnections[sid].turnData.instructionOpcode);
turn = turn.withLanes(fromConnections[sid].turnData.lanes);
if (debug) log("turn ", turn);
actions.push(new wazeOBJ.WazeModelGraphActionsSetTurn(wazeOBJ.wazeModel.getTurnGraph(), fromConnections[sid].withTurnData(turn)));
break;
}
}
for (sid in toConnections){
segId = toConnections[sid].toVertex.segmentID;
log("attr.id: " + attr.id + " --> NodeB: " + ndA.attributes.id + " --> segId: " + segId + " ; state = "+toConnections[sid].turnData.state);
if (debug) log("toConnections["+sid+"] = ", toConnections[sid]);
switch (toConnections[sid].turnData.state){
case 0 :
case 1 :
turn = new wazeOBJ.WazeModelGraphTurnData;
turn = turn.withState(toConnections[sid].turnData.state);
turn = turn.withRestrictions(toConnections[sid].turnData.restrictions);
turn = turn.withInstructionOpcode(toConnections[sid].turnData.instructionOpcode);
turn = turn.withLanes(toConnections[sid].turnData.lanes);
if (debug) log("turn ", turn);
actions.push(new wazeOBJ.WazeModelGraphActionsSetTurn(wazeOBJ.wazeModel.getTurnGraph(), toConnections[sid].withTurnData(turn)));
break;
}
}
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeActionMultiAction(actions));
actions=[];
//on replace les autorisations entrantes
log("ndB", ndB);
for (sid in sconB){
log("sconB: "+sid+" --> nodeA: " + ndB.attributes.id + "--> attr.id: " + attr.id + " ; state = "+sconB[sid].turnData.state);
if (debug) log("sconB["+sid+"]:",sconB[sid]);
switch (sconB[sid].turnData.state){
case 0 :
case 1 :
turn = new wazeOBJ.WazeModelGraphTurnData;
turn = turn.withState(sconB[sid].turnData.state);
turn = turn.withRestrictions(sconB[sid].turnData.restrictions);
turn = turn.withInstructionOpcode(sconB[sid].turnData.instructionOpcode);
turn = turn.withLanes(sconB[sid].turnData.lanes);
if (debug) log("turn ", turn);
actions.push(new wazeOBJ.WazeModelGraphActionsSetTurn(wazeOBJ.wazeModel.getTurnGraph(), sconB[sid].withTurnData(turn)));
break;
}
}
log("ndA", ndA);
for (sid in sconA){
log("sconA : "+sid+" --> nodeB: " + ndA.attributes.id + " --> attr.id: " + attr.id+ " ; state = "+sconA[sid].turnData.state);
if (debug) log("sconA["+sid+"]:",sconA[sid]);
switch (sconA[sid].turnData.state){
case 0 :
case 1 :
turn = new wazeOBJ.WazeModelGraphTurnData;
turn = turn.withState(sconA[sid].turnData.state);
turn = turn.withRestrictions(sconA[sid].turnData.restrictions);
turn = turn.withInstructionOpcode(sconA[sid].turnData.instructionOpcode);
turn = turn.withLanes(sconA[sid].turnData.lanes);
if (debug) log("turn ", turn);
actions.push(new wazeOBJ.WazeModelGraphActionsSetTurn(wazeOBJ.wazeModel.getTurnGraph(), sconA[sid].withTurnData(turn)));
break;
}
}
wazeOBJ.wazeModel.actionManager.add(new wazeOBJ.WazeActionMultiAction(actions));
log('Invert node for segment '+attr.id+': Ok');
}
WRNAB_bootstrap();