WME Route Checker

Allows editors to check the route between two segments

当前为 2015-04-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME Route Checker
  3. // @namespace http://userscripts.org/users/419370
  4. // @description Allows editors to check the route between two segments
  5. // @include https://www.waze.com/editor/*
  6. // @include https://www.waze.com/*/editor/*
  7. // @include https://editor-beta.waze.com/*
  8. // @version 1.11
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // globals
  13. var wmerc_version = "1.11";
  14.  
  15. var AVOID_TOLLS = 1;
  16. var AVOID_FREEWAYS = 2;
  17. var AVOID_DIRT = 4;
  18. var ALLOW_UTURNS = 16;
  19.  
  20. var route_options = ALLOW_UTURNS; // default
  21.  
  22. function showRouteOptions() {
  23. if (Waze.selectionManager.selectedItems.length != 2) {
  24. WMERC_lineLayer_route.destroyFeatures();
  25. WMERC_lineLayer_route.setZIndex(-999);
  26. return false;
  27. }
  28. if (getId('routeOptions') != null) {
  29. return false;
  30. }
  31. // hook into edit panel on the left
  32. var userTabs = getId('edit-panel');
  33. var segmentBox = getElementsByClassName('segment', userTabs)[0];
  34. var tabContent = getElementsByClassName('tab-content', segmentBox)[0];
  35.  
  36. // add new edit tab to left of the map
  37. var addon = document.createElement('div');
  38. addon.id = "routeOptions";
  39. addon.style.borderTop = "solid 2px #E9E9E9";
  40. addon.style.borderBottom = "solid 2px #E9E9E9";
  41. if (location.hostname.match(/editor.*.waze.com/)) {
  42. coords1 = getCoords(Waze.selectionManager.selectedItems[0]);
  43. coords2 = getCoords(Waze.selectionManager.selectedItems[1]);
  44. var url = getLivemap()
  45. + "&from_lon="+coords1.lon + "&from_lat="+coords1.lat
  46. + "&to_lon="+coords2.lon + "&to_lat="+coords2.lat;
  47. addon.innerHTML = '<p><b><a href="'+url+'" title="Opens in new tab" target="LiveMap" style="color:#8309e1">Show routes in LiveMap</a> &raquo;</b></p>';
  48. segmentBox.insertBefore(addon, tabContent);
  49. } else {
  50. addon.innerHTML = '<p><b><a href="#" id="goroutes" title="WME Route Checker v'+wmerc_version+'" style="color:#8309e1">'
  51. + 'Show routes between these two segments</a> &raquo;</b><br>'
  52. + '<b>Avoid:</b>'
  53. + ' <input type="checkbox" id="_avoidTolls" /> Tolls'
  54. + ' <input type="checkbox" id="_avoidFreeways" /> Freeways'
  55. + ' <input type="checkbox" id="_avoidDirt" /> Dirt Trails<br>'
  56. + '<b>Allow:</b>'
  57. + ' <input type="checkbox" id="_allowUTurns" /> U-Turns</p>';
  58. segmentBox.insertBefore(addon, tabContent);
  59. getId('_avoidTolls').checked = route_options & AVOID_TOLLS;
  60. getId('_avoidFreeways').checked = route_options & AVOID_FREEWAYS;
  61. getId('_avoidDirt').checked = route_options & AVOID_DIRT;
  62. getId('_allowUTurns').checked = route_options & ALLOW_UTURNS;
  63. }
  64.  
  65. // create empty div ready for instructions
  66. var routeTest = document.createElement('div');
  67. routeTest.id = "routeTest";
  68. segmentBox.insertBefore(routeTest, tabContent);
  69. // automatically start getting route when user clicks on link
  70. getId('goroutes').onclick = fetchRoute;
  71. return true;
  72. }
  73.  
  74. function saveOptions() {
  75. route_options = (getId('_avoidTolls').checked ? AVOID_TOLLS : 0)
  76. + (getId('_avoidFreeways').checked ? AVOID_FREEWAYS : 0)
  77. + (getId('_avoidDirt').checked ? AVOID_DIRT : 0)
  78. + (getId('_allowUTurns').checked ? ALLOW_UTURNS : 0);
  79.  
  80. console.log("WME Route Checker: saving options: " + route_options);
  81. localStorage.WMERouteChecker = JSON.stringify(route_options);
  82. }
  83.  
  84. function getOptions() {
  85. var list = 'AVOID_TOLL_ROADS' + (route_options & AVOID_TOLLS ? ':t' : ':f') + ','
  86. + 'AVOID_PRIMARIES' + (route_options & AVOID_FREEWAYS ? ':t' : ':f') + ','
  87. + 'AVOID_TRAILS' + (route_options & AVOID_DIRT ? ':t' : ':f') + ','
  88. + 'ALLOW_UTURNS' + (route_options & ALLOW_UTURNS ? ':t' : ':f');
  89. return list;
  90. }
  91.  
  92. function getCoords(segment) {
  93. var numpoints = segment.geometry.components.length;
  94. var middle = Math.floor(numpoints / 2);
  95. if (numpoints % 2 == 1 || numpoints < 2) { // odd number, middle point
  96. seglat = segment.geometry.components[middle].y;
  97. seglon = segment.geometry.components[middle].x;
  98. }
  99. else { // even number - take average of middle two points
  100. seglat = (segment.geometry.components[middle].y
  101. + segment.geometry.components[middle-1].y) / 2.0;
  102. seglon = (segment.geometry.components[middle].x
  103. + segment.geometry.components[middle-1].x) / 2.0;
  104. }
  105. return OpenLayers.Layer.SphericalMercator.inverseMercator(seglon,seglat);
  106. }
  107. function fetchRoute(reverse) {
  108. saveOptions();
  109. var coords1, coord2;
  110. reverse = (reverse != false);
  111. if (reverse) {
  112. coords1 = getCoords(Waze.selectionManager.selectedItems[0]);
  113. coords2 = getCoords(Waze.selectionManager.selectedItems[1]);
  114. } else {
  115. coords1 = getCoords(Waze.selectionManager.selectedItems[1]);
  116. coords2 = getCoords(Waze.selectionManager.selectedItems[0]);
  117. }
  118. var img = '<img src="https://www.waze.com/images/search_indicator.gif" hspace="4">';
  119.  
  120. // get the route, fix and parse the json
  121. getId('routeTest').innerHTML = "<p><b>Fetching route from LiveMap " + img + "</b></p>";
  122. var url = getRoutingManager();
  123. var data = {
  124. from: "x:" + coords1.lon + " y:" + coords1.lat + " bd:true",
  125. to: "x:" + coords2.lon + " y:" + coords2.lat + " bd:true",
  126. returnJSON: true,
  127. returnGeometries: true,
  128. returnInstructions: true,
  129. type: 'HISTORIC_TIME',
  130. clientVersion: '4.0.0',
  131. timeout: 60000,
  132. nPaths: 3,
  133. options: getOptions()};
  134.  
  135. $.ajax({
  136. dataType: "json",
  137. url: url,
  138. data: data,
  139. dataFilter: function(data, dataType) {
  140. return data.replace(/NaN/g, '0');
  141. },
  142. success: function(json) {
  143. showNavigation(json, reverse);
  144. }
  145. });
  146. return false;
  147. }
  148.  
  149. function getLivemap() {
  150. var center_lonlat=new OpenLayers.LonLat(Waze.map.center.lon,Waze.map.center.lat);
  151. center_lonlat.transform(new OpenLayers.Projection ("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
  152. var coords = '?lon='+center_lonlat.lon+'&lat='+center_lonlat.lat;
  153.  
  154. return 'https://www.waze.com/livemap'+coords;
  155. }
  156. function getRoutingManager() {
  157. if (Waze.model.countries.get(235) || Waze.model.countries.get(40)) { // US & Canada
  158. return '/RoutingManager/routingRequest';
  159. } else if (Waze.model.countries.get(106)) { // Israel
  160. return '/il-RoutingManager/routingRequest';
  161. } else {
  162. return '/row-RoutingManager/routingRequest';
  163. }
  164. }
  165. function plotRoute(coords, index) {
  166. var points = [];
  167. for (var i in coords) {
  168. if (i > 0) {
  169. var point = OpenLayers.Layer.SphericalMercator.forwardMercator(coords[i].x, coords[i].y);
  170. points.push(new OL.Geometry.Point(point.lon,point.lat));
  171. }
  172. }
  173. var newline = new OL.Geometry.LineString(points);
  174. var style = {
  175. strokeColor: routeColors[index],
  176. strokeOpacity: 0.7,
  177. strokeWidth: 8 - index * 2,
  178. };
  179. var lineFeature = new OL.Feature.Vector(newline, {type: "routeArrow"}, style);
  180. // Display new segment
  181. WMERC_lineLayer_route.addFeatures([lineFeature]);
  182. }
  183.  
  184. function showNavigation(nav_json, reverse) {
  185. // plot the route
  186. routeColors = ["#8309e1", "#52BAD9", "#888800", ];
  187. WMERC_lineLayer_route.destroyFeatures();
  188. if (typeof nav_json.alternatives !== "undefined"){
  189. for (var r = nav_json.alternatives.length-1; r >= 0; r--) {
  190. plotRoute(nav_json.alternatives[r].coords, r);
  191. }
  192. } else {
  193. plotRoute(nav_json.coords, 0);
  194. }
  195. WMERC_lineLayer_route.setVisibility(true);
  196. WMERC_lineLayer_route.setZIndex(1003);
  197.  
  198. // hide segment details
  199. var userTabs = getId('edit-panel');
  200. var segmentBox = getElementsByClassName('segment', userTabs)[0];
  201. var tabContent = getElementsByClassName('tab-content', segmentBox)[0];
  202. tabContent.style.display = 'none';
  203. // write instructions
  204. instructions = getId('routeTest');
  205. instructions.innerHTML = '';
  206. instructions.style.display = 'block';
  207. instructions.style.height = document.getElementById('map').style.height;
  208.  
  209. if (typeof nav_json.alternatives !== "undefined") {
  210. for (var r = 0; r < nav_json.alternatives.length; r++) {
  211. showInstructions(nav_json.alternatives[r], r);
  212. }
  213. nav_coords = nav_json.alternatives[0].coords;
  214. } else {
  215. showInstructions(nav_json, 0);
  216. nav_coords = nav_json.coords;
  217. }
  218. var lon1 = nav_coords[0].x;
  219. var lat1 = nav_coords[0].y;
  220.  
  221. var end = nav_coords.length - 1;
  222. var lon2 = nav_coords[end].x;
  223. var lat2 = nav_coords[end].y;
  224.  
  225. var rerouteArgs = '{lon:'+lon1+',lat:'+lat1+'},{lon:'+lon2+',lat:'+lat2+'}';
  226.  
  227. // footer for extra links
  228. var footer = document.createElement('div');
  229. footer.className = 'routes_footer';
  230. // create link to reverse the route
  231. var reverseLink = document.createElement('a');
  232. reverseLink.innerHTML = '&laquo; Reverse Route';
  233. reverseLink.href = '#';
  234. reverseLink.setAttribute('onClick', 'fetchRoute('+!reverse+');');
  235. reverseLink.addEventListener('click', function() { fetchRoute(!reverse); }, false);
  236. footer.appendChild(reverseLink);
  237.  
  238. footer.appendChild(document.createTextNode(' | '));
  239.  
  240. var url = getLivemap()
  241. + "&from_lon="+lon1 + "&from_lat="+lat1
  242. + "&to_lon="+lon2 + "&to_lat="+lat2;
  243.  
  244. // create link to view the navigation instructions
  245. var livemapLink = document.createElement('a');
  246. livemapLink.innerHTML = 'View in LiveMap &raquo;';
  247. livemapLink.href = url;
  248. livemapLink.target="LiveMap";
  249. footer.appendChild(livemapLink);
  250.  
  251. footer.appendChild(document.createElement('br'));
  252.  
  253. // add link to script homepage and version
  254. var scriptLink = document.createElement('a');
  255. scriptLink.innerHTML = 'WME Route Checker v' + wmerc_version;
  256. scriptLink.href = 'https://www.waze.com/forum/viewtopic.php?t=64777';
  257. scriptLink.style.fontStyle = 'italic';
  258. scriptLink.target="_blank";
  259. footer.appendChild(scriptLink);
  260.  
  261. instructions.appendChild(footer);
  262.  
  263. return false;
  264. }
  265.  
  266. function showInstructions(nav_json, r) {
  267. var imgRoot = '/assets-editor';
  268. continueImage = '<img src="'+imgRoot+'/images/vectors/routeInstructions/big_direction_forward.png" style="float: left; top: 0; padding-right: 4px" width="16" height="16" />';
  269. beginImage = continueImage;
  270.  
  271. // for each route returned by Waze...
  272. var route = nav_json.response;
  273. var streetNames = route.streetNames;
  274.  
  275. if (r > 0) {
  276. instructions.appendChild(document.createElement('p'));
  277. }
  278.  
  279. // name of the route, with coloured icon
  280. route_name = document.createElement('p');
  281. route_name.className = 'route';
  282. route_name.style.borderColor = routeColors[r];
  283. route_name.innerHTML = '<b style="color:'+routeColors[r]+'">Via ' + route.routeName + '</b>';
  284. instructions.appendChild(route_name);
  285.  
  286. var optail = '';
  287. var prevStreet = '';
  288. var currentItem = null;
  289. var totalDist = 0;
  290. var totalTime = 0;
  291. var prevDist = 0;
  292. var prevTime = 0;
  293. var lastSegment = route.results[0].path.segmentId;
  294.  
  295. // iterate over all the steps in the list
  296. for (var i = 0; i < route.results.length; i++) {
  297. prevDist = totalDist;
  298. prevTime = totalTime;
  299. totalDist += route.results[i].length;
  300. totalTime += route.results[i].crossTime;
  301.  
  302. if (!route.results[i].instruction)
  303. continue;
  304. var opcode = route.results[i].instruction.opcode;
  305. if (!opcode)
  306. continue;
  307.  
  308. if (opcode.match(/ROUNDABOUT_EXIT/))
  309. continue;
  310.  
  311. switch (opcode) {
  312. case "CONTINUE":
  313. case "NONE": dirImage = "big_direction_forward"; break;
  314. case "TURN_LEFT": dirImage = "big_direction_left"; break;
  315. case "TURN_RIGHT": dirImage = "big_direction_right"; break;
  316. case "KEEP_LEFT":
  317. case "EXIT_LEFT": dirImage = "big_direction_exit_left"; break;
  318. case "KEEP_RIGHT":
  319. case "EXIT_RIGHT": dirImage = "big_direction_exit_right"; break;
  320. case "UTURN": dirImage = "big_directions_roundabout_u"; break;
  321. case "APPROACHING_DESTINATION": dirImage = "big_direction_end"; break;
  322. case "ROUNDABOUT_LEFT":
  323. case "ROUNDABOUT_EXIT_LEFT": dirImage = "big_directions_roundabout_l"; break;
  324. case "ROUNDABOUT_RIGHT":
  325. case "ROUNDABOUT_EXIT_RIGHT": dirImage = "big_directions_roundabout_r"; break;
  326. case "ROUNDABOUT_STRAIGHT":
  327. case "ROUNDABOUT_EXIT_STRAIGHT": dirImage = "big_directions_roundabout_s"; break;
  328. case "ROUNDABOUT_ENTER":
  329. case "ROUNDABOUT_EXIT": dirImage = "big_directions_roundabout"; break;
  330. case "ROUNDABOUT_U": dirImage = "big_directions_roundabout_u"; break;
  331. default: dirImage = '';
  332. }
  333. if (dirImage != '')
  334. dirImageSrc = '<img src="'+imgRoot+'/images/vectors/routeInstructions/'+dirImage+'.png" style="float: left; top: 0; padding-right: 4px" width="16" height="16" />';
  335.  
  336. var streetName = route.streetNames[route.results[i].street];
  337. if (!streetName || streetName == null)
  338. streetName = '';
  339. else
  340. streetName = '<span style="color: blue">' + streetName + '</span>';
  341.  
  342. if (streetName.match(/^to/i))
  343. optail = ' ';
  344.  
  345. // display new, non-blank street names
  346. if ((streetName != prevStreet && streetName != '') || i == 0) {
  347. if (currentItem == null) {
  348. // new street that doesn't follow turn instruction, i.e. continue
  349. if (optail == '')
  350. optail = continueImage + 'continue on ';
  351.  
  352. if (i == 0) { // from location
  353. var lon = nav_json.coords[0].x;
  354. var lat = nav_json.coords[0].y;
  355. optail = beginImage + 'depart from ';
  356. } else { // start of street location
  357. var lon = route.results[i].path.x;
  358. var lat = route.results[i].path.y;
  359. }
  360.  
  361. addTurnImage(lat, lon, dirImage);
  362. currentItem = document.createElement('a');
  363. currentItem.className = 'step';
  364. currentItem.innerHTML = optail + streetName;
  365. } else {
  366. // these will be appended to previous turn instruction
  367. currentItem.innerHTML += optail + streetName;
  368. }
  369.  
  370. instructions.appendChild(currentItem);
  371. prevStreet = streetName;
  372. currentItem = null;
  373. optail = '';
  374. }
  375.  
  376. if (opcode == 'CONTINUE' || opcode == 'NONE') {
  377. continue;
  378. }
  379.  
  380. // roundabouts with nth exit instructions
  381. if (opcode == 'ROUNDABOUT_ENTER') {
  382. opcode += route.results[i].instruction.arg + 'th exit';
  383. opcode = opcode.replace(/1th/, '1st');
  384. opcode = opcode.replace(/2th/, '2nd');
  385. opcode = opcode.replace(/3th/, '3rd');
  386. opcode = opcode.replace(/4th/, '4th');
  387. opcode = opcode.replace(/0th/, '<span style="color: red">0th</span>');
  388. }
  389.  
  390. // convert opcode to pretty text
  391. opcode = opcode.replace(/APPROACHING_DESTINATION/, 'arrive at destination');
  392. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?LEFT/, 'at the roundabout, turn left');
  393. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?RIGHT/, 'at the roundabout, turn right');
  394. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?STRAIGHT/, 'at the roundabout, continue straight');
  395. opcode = opcode.replace(/ROUNDABOUT_(ENTER|EXIT)/, 'at the roundabout, take ');
  396. opcode = opcode.toLowerCase().replace(/_/, ' ');
  397. opcode = opcode.replace(/uturn/, 'make a U-turn');
  398. opcode = opcode.replace(/roundabout u/, 'at the roundabout, make a U-turn');
  399.  
  400. // flush previous instruction to list
  401. if (currentItem != null) {
  402. instructions.appendChild(currentItem);
  403. currentItem = null;
  404. }
  405.  
  406. // get coordinates of instruction
  407. if (i+1 < route.results.length) { // location of turn
  408. var lon = route.results[i+1].path.x;
  409. var lat = route.results[i+1].path.y;
  410. } else { // destination location
  411. var end = nav_json.coords.length - 1;
  412. var lon = nav_json.coords[end].x;
  413. var lat = nav_json.coords[end].y;
  414. }
  415. addTurnImage(lat, lon, dirImage);
  416. lastSegment = route.results[i].path.segmentId;
  417.  
  418. // create new instruction
  419. currentItem = document.createElement('a');
  420. currentItem.className = 'step';
  421. currentItem.innerHTML = dirImageSrc + opcode;
  422.  
  423. // connecting word in case we have a street name later
  424. if (opcode.match(/continue/))
  425. optail = ' on ';
  426. else
  427. optail = ' onto ';
  428.  
  429. // force 'turn left' and 'turn right' to show the street name
  430. if (opcode.match(/turn left|turn right/))
  431. prevStreet = '';
  432. }
  433.  
  434. // flush last instruction to list
  435. if (currentItem != null) {
  436. instructions.appendChild(currentItem);
  437. currentItem = null;
  438. }
  439. };
  440.  
  441. function addTurnImage(lat, lon, image, title) {
  442. var coords = OpenLayers.Layer.SphericalMercator.forwardMercator(lon, lat);
  443. var point = new OL.Geometry.Point(coords.lon,coords.lat);
  444. var imgRoot = '/assets-editor';
  445. var style = {
  446. externalGraphic: imgRoot + "/images/vectors/routeInstructions/"+image+".png",
  447. graphicWidth: 30,
  448. graphicHeight: 32,
  449. graphicZIndex: 9999,
  450. title: title
  451. };
  452. var imageFeature = new OL.Feature.Vector(point, null, style);
  453. // Display new segment
  454. WMERC_lineLayer_route.addFeatures([imageFeature]);
  455. }
  456.  
  457. /* helper function */
  458. function getElementsByClassName(classname, node) {
  459. if(!node) node = document.getElementsByTagName("body")[0];
  460. var a = [];
  461. var re = new RegExp('\\b' + classname + '\\b');
  462. var els = node.getElementsByTagName("*");
  463. for (var i=0,j=els.length; i<j; i++)
  464. if (re.test(els[i].className)) a.push(els[i]);
  465. return a;
  466. }
  467.  
  468. function getId(node) {
  469. return document.getElementById(node);
  470. }
  471.  
  472. function initialiseRouteChecker() {
  473. console.log("WME Route Checker: initialising v" + wmerc_version);
  474. if (localStorage.WMERouteChecker) {
  475. route_options = JSON.parse(localStorage.WMERouteChecker);
  476. console.log("WME Route Checker: loaded options: " + route_options);
  477. }
  478.  
  479. /* dirty hack to inject stylesheet in to the DOM */
  480. var style = document.createElement('style');
  481. style.innerHTML = "#routeTest {padding: 0 4px 0 0; overflow-y: auto;}\n"
  482. + "#routeTest p.route {margin: 0; padding: 4px 8px; border-bottom: silver solid 3px; background: #eee}\n"
  483. + "#routeTest a.step {display: block; margin: 0; padding: 3px 8px; text-decoration: none; color:black;border-bottom: silver solid 1px;}\n"
  484. + "#routeTest a.step:hover {background: #ffd; text-decoration: underline;}\n"
  485. + "#routeTest a.step:active {background: #dfd;}\n"
  486. + "#routeTest div.routes_footer {text-align: center; margin-bottom: 25px;}\n";
  487. // + ".WazeControlLayerSwitcher:hover {max-height: 500px}";
  488. (document.body || document.head || document.documentElement).appendChild(style);
  489. // add a new layer for routes
  490. WMERC_lineLayer_route = new OL.Layer.Vector("Route Checker Script",
  491. { rendererOptions: { zIndexing: true },
  492. shortcutKey: "S+t",
  493. uniqueName: 'route_checker' }
  494. );
  495. Waze.map.addLayer(WMERC_lineLayer_route);
  496. Waze.map.addControl(new OL.Control.DrawFeature(WMERC_lineLayer_route, OL.Handler.Path));
  497. // hack in translation:
  498. I18n.translations[I18n.locale].layers.name.route_checker = "Route Checker Script";
  499. // ...and then hide it
  500. $("label:contains('Route Checker Script')").parent().remove();
  501. // add listener for whenever selection changes
  502. Waze.selectionManager.events.register("selectionchanged", null, showRouteOptions);
  503. showRouteOptions(); // for permalinks
  504. }
  505.  
  506. // bootstrap!
  507. (function()
  508. {
  509. setTimeout(initialiseRouteChecker, 1003);
  510. })();
  511.  
  512. /* end ======================================================================= */