WME Route Checker

Allows editors to check the route between two segments

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