WME Route Checker

Allows editors to check the route between two segments

当前为 2023-02-05 提交的版本,查看 最新版本

  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://beta.waze.com/*
  8. // @exclude https://www.waze.com/*user/*editor/*
  9. // @version 1.50
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. // globals
  14. var wmerc_version = "1.50";
  15.  
  16. var AVOID_TOLLS = 1;
  17. var AVOID_FREEWAYS = 2;
  18. var AVOID_DIRT = 4;
  19. var ALLOW_UTURNS = 16;
  20. var VEHICLE_TAXI = 64;
  21. var VEHICLE_BIKE = 128;
  22.  
  23. var route_options = ALLOW_UTURNS; // default
  24.  
  25. var routeColors = ["#8309e1", "#52BAD9", "#888800" ];
  26.  
  27. var WMERC_lineLayer_route;
  28. var WMERC_lineLayer_markers;
  29.  
  30. function addRouteCheckerTab() {
  31. if (W.selectionManager.getSelectedFeatures().length != 2) {
  32. WMERC_lineLayer_route.destroyFeatures();
  33. WMERC_lineLayer_route.setVisibility(false);
  34. WMERC_lineLayer_markers.destroyFeatures();
  35. WMERC_lineLayer_markers.setVisibility(false);
  36. return;
  37. }
  38.  
  39. if (getId('routeOptions') !== null) {
  40. return;
  41. }
  42.  
  43. // hook into edit panel on the left
  44. var navTabs = getId('edit-panel').getElementsByTagName('wz-tabs')[0];
  45.  
  46. var routeTab = document.createElement('wz-tab');
  47. routeTab.id = 'route-checker';
  48. routeTab.className = 'routes-tab';
  49. routeTab.label = 'Routes';
  50. navTabs.appendChild(routeTab);
  51.  
  52. routeTab.addEventListener('tabActive', fetchRoute);
  53.  
  54. // add new edit tab to left of the map
  55. var routeOptions = document.createElement('div');
  56. routeOptions.id = "routeOptions";
  57. routeOptions.style.borderTop = "solid 2px #E9E9E9";
  58. routeOptions.style.borderBottom = "solid 2px #E9E9E9";
  59. routeTab.appendChild(routeOptions);
  60.  
  61. var lang = I18n.translations[I18n.locale];
  62.  
  63. if (location.hostname.match(/editor.*.waze.com/)) {
  64. var coords1 = getCoords(W.selectionManager.getSelectedFeatures()[0]);
  65. var coords2 = getCoords(W.selectionManager.getSelectedFeatures()[1]);
  66. var url = getLivemap()
  67. + `&from_lon=${coords1.lon}&from_lat=${coords1.lat}`
  68. + `&to_lon=${coords2.lon}&to_lat=${coords2.lat}`;
  69.  
  70. routeOptions.innerHTML = '<p><b><a href="'+url+'" title="Opens in new tab" target="LiveMap" style="color:#8309e1">Show routes in LiveMap</a> &raquo;</b></p>';
  71. } else {
  72. routeOptions.innerHTML = `<p><b><a href="#" id="goroutes" title="WME Route Checker v${wmerc_version}" style="color:#8309e1">`
  73. + 'Show routes between these 2 segments</a></b><br>'
  74. + '<b>'+lang.restrictions.editing.driving.dropdowns.vehicle_type+':</b>'
  75. + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_private" value="0" checked> '
  76. + lang.restrictions.vehicle_types.PRIVATE + '</span>'
  77. + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_taxi" value="1"> '
  78. + lang.restrictions.vehicle_types.TAXI + '</span>'
  79. + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_bike" value="1"> '
  80. + lang.restrictions.vehicle_types.MOTORCYCLE + '</span>'
  81. + '<br>'
  82. + '<b>Avoid:</b>'
  83. + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidTolls" /> ' + lang.edit.segment.fields.toll_road + '</span>'
  84. + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidFreeways" /> ' + lang.segment.road_types[3] + '</span>'
  85. + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidDirt" /> ' + lang.edit.segment.fields.unpaved + '</span>'
  86. + '<br>'
  87. + '<b>Allow:</b>'
  88. + ' <input type="checkbox" id="_allowUTurns" /> U-Turns</p>';
  89.  
  90. getId('_avoidTolls').checked = route_options & AVOID_TOLLS;
  91. getId('_avoidFreeways').checked = route_options & AVOID_FREEWAYS;
  92. getId('_avoidDirt').checked = route_options & AVOID_DIRT;
  93. getId('_allowUTurns').checked = route_options & ALLOW_UTURNS;
  94. getId('_vehicleType_taxi').checked = route_options & VEHICLE_TAXI;
  95. getId('_vehicleType_bike').checked = route_options & VEHICLE_BIKE;
  96.  
  97. // automatically start getting route when user clicks on link
  98. getId('goroutes').onclick = fetchRoute;
  99. }
  100.  
  101. routeTab.setAttribute("is-active","false");
  102.  
  103. var tabChange = new MutationObserver(function(mutations) {
  104. mutations.forEach(function(mutation) {
  105. if (mutation.type === "attributes" && mutation.attributeName == "is-active") {
  106. if (mutation.target.isActive) {
  107. var tabs = document.getElementById('edit-panel').getElementsByTagName('wz-tabs')[0].getElementsByTagName('wz-tab');
  108. for (var i=0; i < tabs.length; i++) {
  109. if (tabs[i] != routeTab) {
  110. tabs[i].setAttribute("is-active","false");
  111. }
  112. }
  113. mutation.target.style.display="block";
  114. }
  115. else {
  116. mutation.target.style.display="none";
  117. }
  118. }
  119. });
  120. });
  121. tabChange.observe(routeTab, { attributes: true });
  122.  
  123. // create empty div ready for instructions
  124. var routeTest = document.createElement('div');
  125. routeTest.id = "routeTest";
  126. routeTab.appendChild(routeTest);
  127.  
  128. return;
  129. }
  130.  
  131. function saveOptions() {
  132. route_options = (getId('_avoidTolls').checked ? AVOID_TOLLS : 0)
  133. + (getId('_avoidFreeways').checked ? AVOID_FREEWAYS : 0)
  134. + (getId('_avoidDirt').checked ? AVOID_DIRT : 0)
  135. + (getId('_allowUTurns').checked ? ALLOW_UTURNS : 0)
  136. + (getId('_vehicleType_taxi').checked ? VEHICLE_TAXI : 0)
  137. + (getId('_vehicleType_bike').checked ? VEHICLE_BIKE : 0);
  138.  
  139. console.log("WME Route Checker: saving options: " + route_options);
  140. localStorage.WMERouteChecker = JSON.stringify(route_options);
  141. }
  142.  
  143. function getOptions() {
  144. var list = 'AVOID_TOLL_ROADS' + (route_options & AVOID_TOLLS ? ':t' : ':f') + ','
  145. + 'AVOID_PRIMARIES' + (route_options & AVOID_FREEWAYS ? ':t' : ':f') + ','
  146. + 'AVOID_TRAILS' + (route_options & AVOID_DIRT ? ':t' : ':f') + ','
  147. + 'ALLOW_UTURNS' + (route_options & ALLOW_UTURNS ? ':t' : ':f');
  148. return list;
  149. }
  150.  
  151. function getCoords(segment) {
  152. var numpoints = segment.geometry.components.length;
  153. var middle = Math.floor(numpoints / 2);
  154.  
  155. var seglat, seglon;
  156. if (numpoints % 2 == 1 || numpoints < 2) { // odd number, middle point
  157. seglat = segment.geometry.components[middle].y;
  158. seglon = segment.geometry.components[middle].x;
  159. }
  160. else { // even number - take average of middle two points
  161. seglat = (segment.geometry.components[middle].y
  162. + segment.geometry.components[middle-1].y) / 2.0;
  163. seglon = (segment.geometry.components[middle].x
  164. + segment.geometry.components[middle-1].x) / 2.0;
  165. }
  166. return OpenLayers.Layer.SphericalMercator.inverseMercator(seglon,seglat);
  167. }
  168.  
  169. function fetchRoute(reverse) {
  170. saveOptions();
  171.  
  172. var coords1, coords2;
  173. reverse = (reverse !== false);
  174. var selected = W.selectionManager.getSelectedFeatures();
  175. if (reverse) {
  176. coords1 = getCoords(selected[0]);
  177. coords2 = getCoords(selected[1]);
  178. } else {
  179. coords1 = getCoords(selected[1]);
  180. coords2 = getCoords(selected[0]);
  181. }
  182.  
  183. // get the route, fix and parse the json
  184. getId('routeTest').innerHTML = "<p><b>Fetching route from LiveMap...</b></p>";
  185. var url = getRoutingManager();
  186. var data = {
  187. from: `x:${coords1.lon} y:${coords1.lat} bd:true`,
  188. to: `x:${coords2.lon} y:${coords2.lat} bd:true`,
  189. returnJSON: true,
  190. returnGeometries: true,
  191. returnInstructions: true,
  192. type: 'HISTORIC_TIME',
  193. clientVersion: '4.0.0',
  194. timeout: 60000,
  195. nPaths: 3,
  196. options: getOptions()};
  197.  
  198. if (route_options & VEHICLE_TAXI) {
  199. data.vehicleType = 'TAXI';
  200. }
  201. else if (route_options & VEHICLE_BIKE) {
  202. data.vehicleType = 'MOTORCYCLE';
  203. }
  204. if (window.location.hostname == "beta.waze.com") {
  205. data.id = "beta";
  206. }
  207.  
  208. $.ajax({
  209. dataType: "json",
  210. url: url,
  211. data: data,
  212. dataFilter: function(data, dataType) {
  213. return data.replace(/NaN/g, '0');
  214. },
  215. success: function(json) {
  216. showNavigation(json, reverse);
  217. }
  218. });
  219. return false;
  220. }
  221.  
  222. function getLivemap() {
  223. var center_lonlat=new OpenLayers.LonLat(W.map.getCenter().lon,W.map.getCenter().lat);
  224. center_lonlat.transform(new OpenLayers.Projection ("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
  225. var coords = `?lon=${center_lonlat.lon}&lat=${center_lonlat.lat}`;
  226.  
  227. if (route_options & VEHICLE_TAXI) {
  228. coords += "&rp_vehicleType=TAXI";
  229. }
  230. else if (route_options & VEHICLE_BIKE) {
  231. coords += "&rp_vehicleType=MOTORCYCLE";
  232. }
  233. if (window.location.hostname == "beta.waze.com") {
  234. coords += "&rp_id=beta";
  235. }
  236. coords += "&rp_options=" + getOptions();
  237.  
  238. return `https://www.waze.com/livemap${coords}&overlay=false`;
  239. }
  240.  
  241. function getRoutingManager() {
  242. if (W.model.countries.top.env == "NA") { // Canada, Puerto Rico & US
  243. return '/RoutingManager/routingRequest';
  244. } else if (W.model.countries.top.env == "IL") { // Israel
  245. return '/il-RoutingManager/routingRequest';
  246. } else { // ROW
  247. return '/row-RoutingManager/routingRequest';
  248. }
  249. }
  250.  
  251. function plotRoute(coords, index) {
  252. var points = [];
  253. for (var i in coords) {
  254. if (i > 0) {
  255. var point = OpenLayers.Layer.SphericalMercator.forwardMercator(coords[i].x, coords[i].y);
  256. points.push(new OpenLayers.Geometry.Point(point.lon,point.lat));
  257. }
  258. }
  259. var newline = new OpenLayers.Geometry.LineString(points);
  260.  
  261. var style = {
  262. strokeColor: routeColors[index],
  263. strokeOpacity: 0.7,
  264. strokeWidth: 8 - index * 2
  265. };
  266. var lineFeature = new OpenLayers.Feature.Vector(newline, {type: "routeArrow"}, style);
  267.  
  268. // Display new segment
  269. WMERC_lineLayer_route.addFeatures([lineFeature]);
  270. }
  271.  
  272. function showNavigation(nav_json, reverse) {
  273. WMERC_lineLayer_route.destroyFeatures();
  274. WMERC_lineLayer_route.setVisibility(true);
  275. WMERC_lineLayer_markers.destroyFeatures();
  276. WMERC_lineLayer_markers.setVisibility(true);
  277.  
  278. // write instructions
  279. var instructions = getId('routeTest');
  280. instructions.innerHTML = '';
  281. instructions.style.display = 'block';
  282. instructions.style.height = document.getElementById('map').style.height;
  283.  
  284. var nav_coords;
  285. if (typeof nav_json.alternatives !== "undefined") {
  286. for (var r = 0; r < nav_json.alternatives.length && r < 3; r++) {
  287. showInstructions(instructions, nav_json.alternatives[r], r);
  288. plotRoute(nav_json.alternatives[r].coords, r);
  289. }
  290. nav_coords = nav_json.alternatives[0].coords;
  291. } else {
  292. showInstructions(instructions, nav_json, 0);
  293. plotRoute(nav_json.coords, 0);
  294. nav_coords = nav_json.coords;
  295. }
  296.  
  297. // zoom to show the primary route
  298. //var box = geom.getBounds();
  299. //box = box.transform(W.map.olMap.displayProjection, W.map.getProjectionObject());
  300. //W.map.zoomToExtent(box);
  301.  
  302. var lon1 = nav_coords[0].x;
  303. var lat1 = nav_coords[0].y;
  304.  
  305. var end = nav_coords.length - 1;
  306. var lon2 = nav_coords[end].x;
  307. var lat2 = nav_coords[end].y;
  308.  
  309. var rerouteArgs = `{lon:${lon1},lat:${lat1}},{lon:${lon2},lat:${lat2}}`;
  310.  
  311. // footer for extra links
  312. var footer = document.createElement('div');
  313. footer.className = 'routes_footer';
  314.  
  315. // create link to reverse the route
  316. var reverseLink = document.createElement('a');
  317. reverseLink.innerHTML = '&#8646; Reverse Route';
  318. reverseLink.href = '#';
  319. reverseLink.setAttribute('onClick', 'fetchRoute('+!reverse+');');
  320. reverseLink.addEventListener('click', function() { fetchRoute(!reverse); }, false);
  321. footer.appendChild(reverseLink);
  322.  
  323. footer.appendChild(document.createTextNode(' | '));
  324.  
  325. var url = getLivemap()
  326. + `&from=ll.${lat1},${lon1}`
  327. + `&to=ll.${lat2},${lon2}`;
  328.  
  329. // create link to view the navigation instructions
  330. var livemapLink = document.createElement('a');
  331. livemapLink.innerHTML = 'View in LiveMap &raquo;';
  332. livemapLink.href = url;
  333. livemapLink.target="LiveMap";
  334. footer.appendChild(livemapLink);
  335.  
  336. footer.appendChild(document.createElement('br'));
  337.  
  338. // add link to script homepage and version
  339. var scriptLink = document.createElement('a');
  340. scriptLink.innerHTML = `WME Route Checker v${wmerc_version}`;
  341. scriptLink.href = 'https://www.waze.com/forum/viewtopic.php?t=64777';
  342. scriptLink.style.fontStyle = 'italic';
  343. scriptLink.target="_blank";
  344. footer.appendChild(scriptLink);
  345.  
  346. instructions.appendChild(footer);
  347.  
  348. return false;
  349. }
  350.  
  351. function showInstructions(instructions, nav_json, r) {
  352. // for each route returned by Waze...
  353. var route = nav_json.response;
  354. var streetNames = route.streetNames;
  355.  
  356. if (r > 0) { // divider
  357. instructions.appendChild(document.createElement('p'));
  358. }
  359.  
  360. // name of the route, with coloured icon
  361. var route_name = document.createElement('p');
  362. route_name.className = 'route';
  363. route_name.style.borderColor = routeColors[r];
  364. route_name.innerHTML = `<b style="color:${routeColors[r]}">Via ${route.routeName}</b>`;
  365. if (route.dueToOverride != null) {
  366. route_name.innerHTML += `<br><i>${route.dueToOverride}</i>`;
  367. }
  368. else if (route.isRestricted) {
  369. route_name.innerHTML += `<br><i style="color: darkorange">Restricted Areas: ${route.areas}</i>`;
  370. }
  371. else {
  372. route_name.innerHTML += `<br><i>${route.routeType} Route</i>`;
  373. }
  374. instructions.appendChild(route_name);
  375.  
  376. if (route.tollMeters > 0) {
  377. route_name.innerHTML = '<span style="float: right; background: #88f; color: white; font-size: small">&nbsp;TOLL&nbsp;</span>' + route_name.innerHTML;
  378. }
  379.  
  380. var optail = '';
  381. var prevStreet = '';
  382. var currentItem = null;
  383. var totalDist = 0;
  384. var totalTime = 0;
  385. var isToll = false;
  386. var isRestricted = 0;
  387. //var detourSaving = 0;
  388.  
  389. // street name at starting point
  390. var streetName = streetNames[route.results[0].street];
  391. var departFrom = 'depart';
  392. if (!streetName || streetName === null) {
  393. streetName = '';
  394. }
  395. else {
  396. departFrom = `depart from ${streetName}`;
  397. streetName = ` from <span style="color: blue">${streetName}<span>`;
  398. }
  399.  
  400. // turn icon at starting coordinates
  401. if (r === 0) {
  402. addTurnImageToMap(nav_json.coords[0], getTurnImage('BEGIN'), departFrom);
  403. }
  404.  
  405. // add first instruction (depart)
  406. currentItem = document.createElement('a');
  407. currentItem.className = 'step';
  408. currentItem.innerHTML = getTurnImageSrc(getTurnImage('FORWARD')) + 'depart' + streetName;
  409. instructions.appendChild(currentItem);
  410.  
  411. var segments = [];
  412. // iterate over all the steps in the list
  413. for (var i = 0; i < route.results.length; i++) {
  414. totalDist += route.results[i].length;
  415. totalTime += route.results[i].crossTime;
  416. //detourSaving += route.results[i].detourSavings;
  417.  
  418. segments.push(route.results[i].path.segmentId);
  419.  
  420. if (route.results[i].isToll) {
  421. if (!isToll) {
  422. addMarkerToMap(route.results[i].path, "blue", "Toll");
  423. isToll = true;
  424. }
  425. }
  426. else {
  427. if (isToll) {
  428. addMarkerToMap(route.results[i].path, "blue", "End");
  429. isToll = false;
  430. }
  431. }
  432.  
  433. if (route.results[i].avoidStatus == "AVOID") {
  434. if (isRestricted != route.results[i].areas.length) {
  435. addMarkerToMap(route.results[i].path, 'darkorange', `${route.results[i].areas}`);
  436. isRestricted = route.results[i].areas.length;
  437. }
  438. }
  439. else {
  440. if (isRestricted > 0) {
  441. addMarkerToMap(route.results[i].path, 'darkorange', 'End')
  442. isRestricted = 0;
  443. }
  444. }
  445.  
  446. if (!route.results[i].instruction) {
  447. continue;
  448. }
  449. var opcode = route.results[i].instruction.opcode;
  450. if (!opcode) {
  451. continue;
  452. }
  453.  
  454. // ignore these
  455. if (opcode.match(/ROUNDABOUT_EXIT|NONE/) && route.results[i].instruction.laneGuidance == null) {
  456. continue;
  457. }
  458.  
  459. if (opcode == 'NONE' && !route.results[i].instruction.laneGuidance.enable_display && !route.results[i].instruction.laneGuidance.enable_voice) {
  460. continue; // straight-on is set to 'Waze selected'
  461. }
  462.  
  463. // the image for the turn
  464. var dirImage = getTurnImage(opcode);
  465. var dirImageSrc = '';
  466. if (dirImage !== '') {
  467. dirImageSrc = '';
  468. }
  469.  
  470. // the name that TTS will read out (in blue)
  471. streetName = getNextStreetName(route.results, i, route.streetNames);
  472.  
  473. // roundabouts with nth exit instructions
  474. if (opcode == 'ROUNDABOUT_ENTER') {
  475. opcode += route.results[i].instruction.arg + 'th exit';
  476. opcode = opcode.replace(/1th/, '1st');
  477. opcode = opcode.replace(/2th/, '2nd');
  478. opcode = opcode.replace(/3th/, '3rd');
  479. }
  480.  
  481. // convert opcode to pretty text
  482. opcode = opcode.replace(/APPROACHING_DESTINATION/, 'arrive');
  483. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?LEFT/, 'at the roundabout, turn left');
  484. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?RIGHT/, 'at the roundabout, turn right');
  485. opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?STRAIGHT/, 'at the roundabout, continue straight');
  486. opcode = opcode.replace(/ROUNDABOUT_ENTER/, 'at the roundabout, take ');
  487. opcode = opcode.toLowerCase().replace(/_/, ' ');
  488. opcode = opcode.replace(/uturn/, 'make a U-turn');
  489. opcode = opcode.replace(/roundabout u/, 'at the roundabout, make a U-turn');
  490.  
  491. // convert keep to exit if needed
  492. var keepSide = W.model.isLeftHand ? /keep left/ : /keep right/;
  493. if (opcode.match(keepSide) && i+1 < route.results.length &&
  494. isKeepForExit(route.results[i].roadType, route.results[i+1].roadType)) {
  495. opcode = opcode.replace(/keep (.*)/, 'exit $1');
  496. }
  497.  
  498. var laneInfo = "";
  499. var laneIcon = "";
  500. if (route.results[i].clientLaneSet != null) {
  501. var lanes = route.results[i].clientLaneSet.client_lane;
  502. var guide = route.results[i].instruction.laneGuidance;
  503. laneInfo += " |";
  504. for (var l = 0; l < lanes.length; l++) {
  505. if (l > 0) {
  506. laneInfo += "\u2506"; // dashed line
  507. }
  508. var laneArrow = "\u2001"; // space \u00A0
  509. for (var a = 0; a < lanes[l].angle_object.length; a++) {
  510. var lane = lanes[l].angle_object[a];
  511. if (lane.selected) {
  512. laneArrow = getLaneArrow(lane.angle);
  513. }
  514. }
  515. laneInfo += ` ${laneArrow} `;
  516. laneIcon += laneArrow != '\u2001' ? laneArrow : '.';
  517. }
  518. laneInfo += "| ";
  519. if (guide != null && opcode == 'none') {
  520. if (lanes.enable_voice_for_instruction) {
  521. laneInfo += "\uD83D\uDD08\uD83D\uDD08"; // View and hear
  522. }
  523. if (guide.enable_voice) {
  524. laneInfo += "\uD83D\uDD08"; // View and hear
  525. }
  526. else if (guide.enable_display) {
  527. laneInfo += "\uD83D\uDC41"; // View only
  528. }
  529. }
  530. }
  531.  
  532. // show turn symbol on the map (for first route only)
  533. if (r === 0) {
  534. var title;
  535. if (opcode == 'arrive') {
  536. var end = nav_json.coords.length - 1;
  537. title = 'arrive at ' + (streetName !== '' ? streetName : 'destination');
  538. addTurnImageToMap(nav_json.coords[end], dirImage, title);
  539. }
  540. else if (opcode != 'none') {
  541. title = opcode.replace(/at the roundabout, /, '');
  542. if (streetName !== '') title += ` onto ${streetName}`;
  543. if (laneIcon !== '') title = ` \u2502${laneIcon}\u2502 \u00A0 ${title}`;
  544. addTurnImageToMap(route.results[i+1].path, dirImage, title);
  545. }
  546. else if (laneInfo != '') {
  547. addTurnImageToMap(route.results[i+1].path, null, `\u2502${laneIcon}\u2502`);
  548. }
  549. }
  550.  
  551. // pretty street name
  552. if (streetName !== '') {
  553. if (opcode == 'arrive') {
  554. streetName = ` at <span style="color: blue">${streetName}</span>`;
  555. }
  556. else if (opcode != 'none') {
  557. streetName = ` onto <span style="color: blue">${streetName}</span>`;
  558. }
  559. }
  560.  
  561. if (laneInfo != '') {
  562. laneInfo = "<div align='center'>" + laneInfo + "</div>";
  563. }
  564.  
  565. // display new instruction
  566. currentItem = document.createElement('a');
  567. currentItem.className = 'step';
  568. if (opcode != 'none') {
  569. currentItem.innerHTML = getTurnImageSrc(dirImage) + opcode + streetName + laneInfo;
  570. }
  571. else {
  572. currentItem.innerHTML = laneInfo;
  573. }
  574. if (opcode.match(/0th exit/)) {
  575. currentItem.style.color = 'red';
  576. }
  577. instructions.appendChild(currentItem);
  578. }
  579.  
  580. // append distance and time to last instruction
  581. currentItem.title = `${(totalDist/1609).toFixed(3)} miles`;
  582. currentItem.innerHTML += ` - ${totalDist/1000} km`;
  583. currentItem.innerHTML += ` - ${timeFromSecs(totalTime)}`;
  584. //if (detourSaving > 0) {
  585. // currentItem.innerHTML += '<br>&nbsp; <i>detour saved ' + timeFromSecs(detourSaving) + '</i>';
  586. //}
  587.  
  588. var selectAll = document.createElement('a');
  589. selectAll.className = 'step select';
  590. selectAll.innerHTML = 'Select route segments &#8605;';
  591. selectAll.href = "#";
  592. selectAll.addEventListener('click', function() { selectSegmentIDs(segments); }, false);
  593. instructions.appendChild(selectAll);
  594. }
  595.  
  596. function getLaneArrow(angle)
  597. {
  598. switch (angle) {
  599. case -180: return "\u21B6";
  600. case -135: return "\u2199";
  601. case -90: return "\u21B0";
  602. case -45: return "\u2196";
  603. case -0: return "\u2191";
  604. case 45: return "\u2197";
  605. case 90: return "\u21B1";
  606. case 135: return "\u2198";
  607. case 180: return "\u21B7";
  608. default: return angle;
  609. }
  610. }
  611.  
  612. function selectSegmentIDs(segments) {
  613. var objects = [];
  614. for (var i = 0; i < segments.length; i++) {
  615. var segment = W.model.segments.getObjectById(segments[i]);
  616. if (segment != null) {
  617. objects.push(segment);
  618. }
  619. }
  620. W.selectionManager.setSelectedModels(objects);
  621. return false;
  622. }
  623.  
  624. function getNextStreetName(results, index, streetNames) {
  625. var streetName = '';
  626. var unnamedCount = 0;
  627. var unnamedLength = 0;
  628.  
  629. // destination
  630. if (index == results.length-1) {
  631. streetName = streetNames[results[index].street];
  632. if (!streetName || streetName === null) {
  633. streetName = '';
  634. }
  635. }
  636.  
  637. // look ahead to next street name
  638. while (++index < results.length && streetName === '') {
  639. streetName = streetNames[results[index].street];
  640. if (!streetName || streetName === null) {
  641. streetName = '';
  642. }
  643.  
  644. // "Navigation instructions for unnamed segments" <- in the Wiki
  645. if (streetName === '' && !isFreewayOrRamp(results[index].roadType)
  646. && !isRoundabout(results[index].path.segmentId)) {
  647. unnamedLength += length;
  648. unnamedCount++;
  649. if (unnamedCount >= 4 || unnamedLength >= 400) {
  650. //console.log("- unnamed segments too long; break");
  651. break;
  652. }
  653. }
  654. }
  655.  
  656. return streetName;
  657. }
  658.  
  659. function getTurnImage(opcode) {
  660. var dirImage = '';
  661. if (W.model.getTopCountry().leftHandTraffic) {
  662. opcode = opcode.replace(/(ROUNDABOUT_)(EXIT_)?/, "$1UK_");
  663. }
  664. switch (opcode) {
  665. case "BEGIN": return 'https://www.waze.com/livemap3/assets/pin-9ad4ceb21a2449b4d0bcacdcf464f015.png';
  666. case "CONTINUE":
  667. case "NONE": dirImage = "big_direction_forwardc0958c4d4c5c79bcb656d34f3afb3ea2.png"; break;
  668. case "TURN_LEFT": dirImage = "big_direction_left5b94fa33f945d46ab1bdd1131ac0457e.png"; break;
  669. case "TURN_RIGHT": dirImage = "big_direction_right2d403871f04763260a40c537e231897e.png"; break;
  670. case "KEEP_LEFT":
  671. case "EXIT_LEFT": dirImage = "big_direction_exit_left1c1498a6dec9582bae81d34ec9e6dc3b.png"; break;
  672. case "KEEP_RIGHT":
  673. case "EXIT_RIGHT": dirImage = "big_direction_exit_rightba4fee1380f556a8570252c6745f1442.png"; break;
  674. case "UTURN": dirImage = "big_direction_u63cf785b68a57e8663020098cd07ed76.png"; break;
  675. case "APPROACHING_DESTINATION": dirImage = "big_direction_end25226c71aed0efd3a2db41978066febc.png"; break;
  676. case "ROUNDABOUT_LEFT":
  677. case "ROUNDABOUT_EXIT_LEFT": dirImage = "big_directions_roundabout_l54dc48b91e36549b26bae30135462780.png"; break;
  678. case "ROUNDABOUT_UK_LEFT": dirImage = "big_directions_roundabout_UK_ldc86a0b99cfcd4ed03b0192d5b350c70.png"; break;
  679. case "ROUNDABOUT_RIGHT":
  680. case "ROUNDABOUT_EXIT_RIGHT": dirImage = "big_directions_roundabout_rc114740b6cafc42177a53aa6c803c14d.png"; break;
  681. case "ROUNDABOUT_UK_RIGHT": dirImage = "big_directions_roundabout_r_UKc34794c4d01ec8a9fa012150d2f1e02a.png"; break;
  682. case "ROUNDABOUT_STRAIGHT":
  683. case "ROUNDABOUT_EXIT_STRAIGHT": dirImage = "big_directions_roundabout_sffadf4fd7b277b8ef2f21688e79b9351.png"; break;
  684. case "ROUNDABOUT_UK_STRAIGHT": dirImage = "big_directions_roundabout_UK_s01ea5c47f4e08b20532505d84b3271e0.png"; break;
  685. case "ROUNDABOUT_ENTER":
  686. case "ROUNDABOUT_EXIT": dirImage = "big_directions_roundabout9f9bf37022d431be50fecc457cd6e3df.png"; break;
  687. case "ROUNDABOUT_UK_ENTER":
  688. case "ROUNDABOUT_UK_EXIT": dirImage = "big_directions_roundabout_UK7dce607d7359326a799fd9d3ad8542aa.png"; break;
  689. case "ROUNDABOUT_U": dirImage = "big_directions_roundabout_u3634283a7d740f30eb18c203f6a357be.png"; break;
  690. case "ROUNDABOUT_UK_U": dirImage = "big_directions_roundabout_u_UKba204c8a12885976f9bc5b07165b8644.png"; break;
  691. default: return '';
  692. }
  693. return 'https://editor-assets.waze.com/production/img/' + dirImage;
  694. }
  695.  
  696. function getTurnImageSrc(dirImage) {
  697. if (dirImage !== '') {
  698. return '<img src="' + dirImage + '" style="float: left; top: 0; padding-right: 4px" width="16" height="16" />';
  699. }
  700. return '';
  701. }
  702.  
  703. function isKeepForExit(fromType, toType) {
  704. // primary to non-primary
  705. if (isPrimaryRoad(fromType) && !isPrimaryRoad(toType)) {
  706. return true;
  707. }
  708. // ramp to non-primary or non-ramp
  709. if (isRamp(fromType) && !isPrimaryRoad(toType) && !isRamp(toType)) {
  710. return true;
  711. }
  712. return false;
  713. }
  714.  
  715. function isFreewayOrRamp(t) {
  716. return t === 3 /*FREEWAY*/ || t === 4 /*RAMP*/;
  717. }
  718.  
  719. function isPrimaryRoad(t) {
  720. return t === 3 /*FREEWAY*/ || t === 6 /*MAJOR_HIGHWAY*/ || t === 7 /*MINOR_HIGHWAY*/;
  721. }
  722.  
  723. function isRamp(t) {
  724. return t === 4 /*RAMP*/;
  725. }
  726.  
  727. function isRoundabout(id) {
  728. var segment = W.model.segments.getObjectById(id);
  729. if (segment != null) {
  730. return segment.attributes.junctionId !== null;
  731. }
  732. return false;
  733. }
  734.  
  735. function timeFromSecs(seconds)
  736. {
  737. var hh = '00'+Math.floor(((seconds/86400)%1)*24);
  738. var mm = '00'+Math.floor(((seconds/3600)%1)*60);
  739. var ss = '00'+Math.round(((seconds/60)%1)*60);
  740. return hh.slice(-2) + ':' + mm.slice(-2) + ':' + ss.slice(-2);
  741. }
  742.  
  743. function addTurnImageToMap(location, image, title) {
  744. if (image === '') return;
  745.  
  746. var coords = OpenLayers.Layer.SphericalMercator.forwardMercator(location.x, location.y);
  747. var point = new OpenLayers.Geometry.Point(coords.lon,coords.lat);
  748.  
  749. var style = {
  750. externalGraphic: image,
  751. graphicWidth: 30,
  752. graphicHeight: 32,
  753. label: title,
  754. labelXOffset: 20,
  755. labelAlign: 'left',
  756. labelOutlineColor: 'white',
  757. labelOutlineWidth: 3,
  758. fontWeight: 'bold',
  759. fontColor: routeColors[0]
  760. };
  761.  
  762. if (title.match(/0th exit/)) {
  763. style.fontColor = 'red';
  764. }
  765.  
  766. var imageFeature = new OpenLayers.Feature.Vector(point, null, style);
  767. WMERC_lineLayer_markers.addFeatures([imageFeature]);
  768.  
  769. if (image === null) {
  770. style = {
  771. label: '●',
  772. labelAlign: 'center',
  773. labelOutlineColor: 'white',
  774. labelOutlineWidth: 3,
  775. fontWeight: 'bold',
  776. fontColor: routeColors[0],
  777. fontSize: '20pt'
  778. };
  779.  
  780. imageFeature = new OpenLayers.Feature.Vector(point, null, style);
  781. WMERC_lineLayer_route.addFeatures([imageFeature]);
  782. }
  783. }
  784.  
  785. function addMarkerToMap(location, color, title) {
  786. var coords = OpenLayers.Layer.SphericalMercator.forwardMercator(location.x, location.y);
  787. var point = new OpenLayers.Geometry.Point(coords.lon,coords.lat);
  788.  
  789. var style = {
  790. label: title,
  791. labelAlign: 'right',
  792. labelOutlineColor: color,
  793. labelOutlineWidth: 3,
  794. labelXOffset: -16,
  795. fontWeight: 'bold',
  796. fontColor: 'white',
  797. strokeColor: color,
  798. strokeWidth: 2,
  799. fillColor: 'white'
  800. };
  801.  
  802. if (color == 'blue') {
  803. style.labelAlign = 'center';
  804. style.labelXOffset = 0;
  805. style.labelYOffset = -20;
  806. }
  807.  
  808. var imageFeature = new OpenLayers.Feature.Vector(point, null, style);
  809. WMERC_lineLayer_route.addFeatures([imageFeature]);
  810.  
  811. style = {
  812. labelAlign: 'center',
  813. labelOutlineColor: color,
  814. labelOutlineWidth: 3,
  815. fontWeight: 'bold',
  816. fontColor: 'white'
  817. };
  818.  
  819. if (title != 'End') {
  820. style.label = '●';
  821. style.fontSize = '20pt';
  822. }
  823. else {
  824. style.label = '⊘';
  825. }
  826.  
  827. imageFeature = new OpenLayers.Feature.Vector(point, null, style);
  828. WMERC_lineLayer_route.addFeatures([imageFeature]);
  829. }
  830.  
  831. /* helper function */
  832. function getElementsByClassName(classname, node) {
  833. if(!node) node = document.getElementsByTagName("body")[0];
  834. var a = [];
  835. var re = new RegExp('\\b' + classname + '\\b');
  836. var els = node.getElementsByTagName("*");
  837. for (var i=0,j=els.length; i<j; i++) {
  838. if (re.test(els[i].className)) {
  839. a.push(els[i]);
  840. }
  841. }
  842. return a;
  843. }
  844.  
  845. function getId(node) {
  846. return document.getElementById(node);
  847. }
  848.  
  849. function initialiseRouteChecker() {
  850. if (typeof W == 'undefined') {
  851. return; // not WME
  852. }
  853.  
  854. console.log("WME Route Checker: initialising v" + wmerc_version);
  855.  
  856. if (localStorage.WMERouteChecker) {
  857. route_options = JSON.parse(localStorage.WMERouteChecker);
  858. console.log("WME Route Checker: loaded options: " + route_options);
  859. }
  860.  
  861. /* dirty hack to inject stylesheet in to the DOM */
  862. var style = document.createElement('style');
  863. style.innerHTML = "#routeTest {padding: 0 4px 0 0; overflow-y: auto;}\n"
  864. + "#routeTest p.route {margin: 0; padding: 4px 8px; border-bottom: silver solid 3px; background: #eee}\n"
  865. + "#routeTest a.step {display: block; margin: 0; padding: 3px 8px; text-decoration: none; color:black;border-bottom: silver solid 1px;}\n"
  866. + "#routeTest a.step:hover {background: #ffd;}\n"
  867. + "#routeTest a.step:active {background: #dfd;}\n"
  868. + "#routeTest a.select {color: #00f; text-align: right}\n"
  869. + "#routeTest div.routes_footer {text-align: center; margin-bottom: 25px;}\n";
  870. (document.body || document.head || document.documentElement).appendChild(style);
  871.  
  872. // add a new layer for routes
  873. WMERC_lineLayer_route = new OpenLayers.Layer.Vector("Route Checker Script",
  874. { displayInLayerSwitcher: false,
  875. uniqueName: 'route_checker' }
  876. );
  877. W.map.addLayer(WMERC_lineLayer_route);
  878.  
  879. // add a new layer for markers
  880. WMERC_lineLayer_markers = new OpenLayers.Layer.Vector("Route Checker Script Markers",
  881. { displayInLayerSwitcher: false,
  882. uniqueName: 'route_checker2' }
  883. );
  884. W.map.addLayer(WMERC_lineLayer_markers);
  885.  
  886. var observer = new MutationObserver(function(mutations) {
  887. mutations.forEach(function(mutation) {
  888. if (mutation.addedNodes.length > 0 && mutation.target.className == 'contents') {
  889. addRouteCheckerTab();
  890. }
  891. });
  892. });
  893. observer.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
  894. }
  895.  
  896. // bootstrap!
  897. (function()
  898. {
  899. setTimeout(initialiseRouteChecker, 1003);
  900. })();
  901.  
  902. /* end ======================================================================= */