Serves to reverse the nodes A and B of a segment, in order to better manage restrictions on multiples segments.
// ==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();