WME Route Checker

Allows editors to check the route between two segments

Versione datata 09/09/2015. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name                WME Route Checker
// @namespace           http://userscripts.org/users/419370
// @description         Allows editors to check the route between two segments
// @include             https://www.waze.com/editor/*
// @include             https://www.waze.com/*/editor/*
// @include             https://editor-beta.waze.com/*
// @version             1.16
// @grant               none
// ==/UserScript==

// globals
var wmerc_version = "1.16";

var AVOID_TOLLS = 1;
var AVOID_FREEWAYS = 2;
var AVOID_DIRT = 4;
var ALLOW_UTURNS = 16;
var ROUTE_DIST = 32;

var route_options = ALLOW_UTURNS; // default

var routeZIndex = 0;

var routeColors = ["#8309e1", "#52BAD9", "#888800" ];


function showRouteOptions() {
  if (Waze.selectionManager.selectedItems.length != 2) {
    WMERC_lineLayer_route.destroyFeatures();
    WMERC_lineLayer_route.setZIndex(-999);
    return;
  }

  if (getId('routeOptions') !== null) {
    return;
  }

  // hook into edit panel on the left
  var userTabs = getId('edit-panel');
  var segmentBox = getElementsByClassName('segment', userTabs)[0];
  var tabContent = getElementsByClassName('tab-content', segmentBox)[0];

  // add new edit tab to left of the map
  var addon = document.createElement('div');
  addon.id = "routeOptions";
  addon.style.borderTop = "solid 2px #E9E9E9";
  addon.style.borderBottom = "solid 2px #E9E9E9";
  if (location.hostname.match(/editor.*.waze.com/)) {
    var coords1 = getCoords(Waze.selectionManager.selectedItems[0]);
    var coords2 = getCoords(Waze.selectionManager.selectedItems[1]);
    var url = getLivemap()
            + "&from_lon="+coords1.lon + "&from_lat="+coords1.lat
            + "&to_lon="+coords2.lon + "&to_lat="+coords2.lat;

    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>';
    segmentBox.insertBefore(addon, tabContent);
  } else {
    addon.innerHTML = '<p><b><a href="#" id="goroutes" title="WME Route Checker v'+wmerc_version+'" style="color:#8309e1">'
                    + 'Show routes between these two segments</a> &raquo;</b><br>'
                    + '<b>Route:</b>'
                    + ' <input type="radio" name="_routeType" id="_routeType_fast" value="0" checked> Fastest'
                    + ' <input type="radio" name="_routeType" id="_routeType_short" value="'+ROUTE_DIST+'"> Shortest<br>'
                    + '<b>Avoid:</b>'
                    + ' <input type="checkbox" id="_avoidTolls" /> Tolls'
                    + ' <input type="checkbox" id="_avoidFreeways" /> Freeways'
                    + ' <input type="checkbox" id="_avoidDirt" /> Dirt Trails<br>'
                    + '<b>Allow:</b>'
                    + ' <input type="checkbox" id="_allowUTurns" /> U-Turns</p>';
     segmentBox.insertBefore(addon, tabContent);

     getId('_avoidTolls').checked              = route_options & AVOID_TOLLS;
     getId('_avoidFreeways').checked           = route_options & AVOID_FREEWAYS;
     getId('_avoidDirt').checked               = route_options & AVOID_DIRT;
     getId('_allowUTurns').checked             = route_options & ALLOW_UTURNS;
     getId('_routeType_short').checked         = route_options & ROUTE_DIST;
  }

  // create empty div ready for instructions
  var routeTest = document.createElement('div');
  routeTest.id = "routeTest";
  segmentBox.insertBefore(routeTest, tabContent);

  // automatically start getting route when user clicks on link
  getId('goroutes').onclick = fetchRoute;

  return;
}

function saveOptions() {
  route_options = (getId('_avoidTolls').checked    ? AVOID_TOLLS    : 0)
                + (getId('_avoidFreeways').checked ? AVOID_FREEWAYS : 0)
                + (getId('_avoidDirt').checked     ? AVOID_DIRT     : 0)
                + (getId('_allowUTurns').checked   ? ALLOW_UTURNS   : 0)
                + (getId('_routeType_short').checked ? ROUTE_DIST   : 0);

  console.log("WME Route Checker: saving options: " + route_options);
  localStorage.WMERouteChecker = JSON.stringify(route_options);
}

function getOptions() {
  var list = 'AVOID_TOLL_ROADS' + (route_options & AVOID_TOLLS    ? ':t' : ':f') + ','
           + 'AVOID_PRIMARIES'  + (route_options & AVOID_FREEWAYS ? ':t' : ':f') + ','
           + 'AVOID_TRAILS'     + (route_options & AVOID_DIRT     ? ':t' : ':f') + ','
           + 'ALLOW_UTURNS'     + (route_options & ALLOW_UTURNS   ? ':t' : ':f');
  return list;
}

function getCoords(segment) {
  var numpoints = segment.geometry.components.length;
  var middle = Math.floor(numpoints / 2);

  var seglat, seglon;
  if (numpoints % 2 == 1 || numpoints < 2) { // odd number, middle point
    seglat = segment.geometry.components[middle].y;
    seglon = segment.geometry.components[middle].x;
  }
  else { // even number - take average of middle two points
    seglat = (segment.geometry.components[middle].y
           +  segment.geometry.components[middle-1].y) / 2.0;
    seglon = (segment.geometry.components[middle].x
           +  segment.geometry.components[middle-1].x) / 2.0;
  }
  return OpenLayers.Layer.SphericalMercator.inverseMercator(seglon,seglat);
}

function fetchRoute(reverse) {
  saveOptions();

  var coords1, coords2;
  reverse = (reverse !== false);
  if (reverse) {
    coords1 = getCoords(Waze.selectionManager.selectedItems[0]);
    coords2 = getCoords(Waze.selectionManager.selectedItems[1]);
  } else {
    coords1 = getCoords(Waze.selectionManager.selectedItems[1]);
    coords2 = getCoords(Waze.selectionManager.selectedItems[0]);
  }

  var img = '<img src="https://www.waze.com/images/search_indicator.gif" hspace="4">';

  // get the route, fix and parse the json
  getId('routeTest').innerHTML = "<p><b>Fetching route from LiveMap " + img + "</b></p>";
  var url = getRoutingManager();
  var data = {
    from: "x:" + coords1.lon + " y:" + coords1.lat + " bd:true",
    to: "x:" + coords2.lon + " y:" + coords2.lat + " bd:true",
    returnJSON: true,
    returnGeometries: true,
    returnInstructions: true,
    type: (route_options & ROUTE_DIST ? 'DISTANCE' : 'HISTORIC_TIME'),
    clientVersion: '4.0.0',
    timeout: 60000,
    nPaths: 3,
    options: getOptions()};

  $.ajax({
    dataType: "json",
    url: url,
    data: data,
    dataFilter: function(data, dataType) {
      return data.replace(/NaN/g, '0');
    },
    success: function(json) {
      showNavigation(json, reverse);
    }
  });
  return false;
}

function getLivemap() {
  var center_lonlat=new OpenLayers.LonLat(Waze.map.center.lon,Waze.map.center.lat);
  center_lonlat.transform(new OpenLayers.Projection ("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
  var coords = '?lon='+center_lonlat.lon+'&lat='+center_lonlat.lat;

  return 'https://www.waze.com/livemap'+coords;
}

function getRoutingManager() {
  if (Waze.model.countries.get(235) || Waze.model.countries.get(40)) { // US & Canada
    return '/RoutingManager/routingRequest';
  } else if (Waze.model.countries.get(106)) { // Israel
    return '/il-RoutingManager/routingRequest';
  } else {
    return '/row-RoutingManager/routingRequest';
  }
}

function plotRoute(coords, index) {
  var points = [];
  for (var i in coords) {
    if (i > 0) {
      var point = OpenLayers.Layer.SphericalMercator.forwardMercator(coords[i].x, coords[i].y);
      points.push(new OL.Geometry.Point(point.lon,point.lat));
    }
  }
  var newline = new OL.Geometry.LineString(points);

  var style = {
    strokeColor: routeColors[index],
    strokeOpacity: 0.7,
    strokeWidth: 8 - index * 2,
  };
  var lineFeature = new OL.Feature.Vector(newline, {type: "routeArrow"}, style);

  // Display new segment
  WMERC_lineLayer_route.addFeatures([lineFeature]);
}

function showNavigation(nav_json, reverse) {
  // plot the route
  WMERC_lineLayer_route.destroyFeatures();
  if (typeof nav_json.alternatives !== "undefined"){
    for (var r = nav_json.alternatives.length-1; r >= 0; r--) {
      plotRoute(nav_json.alternatives[r].coords, r);
    }
  } else {
    plotRoute(nav_json.coords, 0);
  }
  WMERC_lineLayer_route.setVisibility(true);
  WMERC_lineLayer_route.setZIndex(routeZIndex);

  // hide segment details
  var userTabs = getId('edit-panel');
  var segmentBox = getElementsByClassName('segment', userTabs)[0];
  var tabContent = getElementsByClassName('tab-content', segmentBox)[0];
  tabContent.style.display = 'none';

  // write instructions
  instructions = getId('routeTest');
  instructions.innerHTML = '';
  instructions.style.display = 'block';
  instructions.style.height = document.getElementById('map').style.height;

  var nav_coords;
  if (typeof nav_json.alternatives !== "undefined") {
    for (var r = 0; r < nav_json.alternatives.length; r++) {
      showInstructions(nav_json.alternatives[r], r);
    }
    nav_coords = nav_json.alternatives[0].coords;
  } else {
    showInstructions(nav_json, 0);
    nav_coords = nav_json.coords;
  }
  var lon1 = nav_coords[0].x;
  var lat1 = nav_coords[0].y;

  var end = nav_coords.length - 1;
  var lon2 = nav_coords[end].x;
  var lat2 = nav_coords[end].y;

  var rerouteArgs = '{lon:'+lon1+',lat:'+lat1+'},{lon:'+lon2+',lat:'+lat2+'}';

  // footer for extra links
  var footer = document.createElement('div');
  footer.className = 'routes_footer';

  // create link to reverse the route
  var reverseLink = document.createElement('a');
  reverseLink.innerHTML = '&laquo; Reverse Route';
  reverseLink.href = '#';
  reverseLink.setAttribute('onClick', 'fetchRoute('+!reverse+');');
  reverseLink.addEventListener('click', function() { fetchRoute(!reverse); }, false);
  footer.appendChild(reverseLink);

  footer.appendChild(document.createTextNode(' | '));

  var url = getLivemap()
          + "&from_lon="+lon1 + "&from_lat="+lat1
          + "&to_lon="+lon2 + "&to_lat="+lat2;

  // create link to view the navigation instructions
  var livemapLink = document.createElement('a');
  livemapLink.innerHTML = 'View in LiveMap &raquo;';
  livemapLink.href = url;
  livemapLink.target="LiveMap";
  footer.appendChild(livemapLink);

  footer.appendChild(document.createElement('br'));

  // add link to script homepage and version
  var scriptLink = document.createElement('a');
  scriptLink.innerHTML = 'WME Route Checker v' + wmerc_version;
  scriptLink.href = 'https://www.waze.com/forum/viewtopic.php?t=64777';
  scriptLink.style.fontStyle = 'italic';
  scriptLink.target="_blank";
  footer.appendChild(scriptLink);

  instructions.appendChild(footer);

  return false;
}

function showInstructions(nav_json, r) {
  // for each route returned by Waze...
  var route = nav_json.response;
  var streetNames = route.streetNames;

  if (r > 0) { // divider
    instructions.appendChild(document.createElement('p'));
  }

  // name of the route, with coloured icon
  var route_name = document.createElement('p');
  route_name.className = 'route';
  route_name.style.borderColor = routeColors[r];
  route_name.innerHTML = '<b style="color:'+routeColors[r]+'">Via ' + route.routeName + '</b>';
  instructions.appendChild(route_name);

  if (route.tollMeters > 0) {
    route_name.innerHTML = '<span style="float: right">TOLL</span>' + route_name.innerHTML;
  }

  var optail = '';
  var prevStreet = '';
  var currentItem = null;
  var totalDist = 0;
  var totalTime = 0;
  //var detourSaving = 0;
  
  // street name at starting point
  var streetName = streetNames[route.results[0].street];
  var departFrom = 'depart';
  if (!streetName || streetName === null)
    streetName = '';
  else {
    departFrom = 'depart from ' + streetName;
    streetName = ' from <span style="color: blue">' + streetName + '<span>';
  }
  
  // turn icon at starting coordinates
  if (r == 0) {
    addTurnImageToMap(nav_json.coords[0], "big_direction_forward", departFrom);
  }

  // add first instruction (depart)
  currentItem = document.createElement('a');
  currentItem.className = 'step';
  currentItem.innerHTML = getTurnImageSrc('big_direction_forward') + 'depart' + streetName;
  instructions.appendChild(currentItem);

  // iterate over all the steps in the list
  for (var i = 0; i < route.results.length; i++) {
    totalDist += route.results[i].length;
    totalTime += route.results[i].crossTime;
    //detourSaving += route.results[i].detourSavings;

    if (!route.results[i].instruction)
      continue;
    var opcode = route.results[i].instruction.opcode;
    if (!opcode)
      continue;

    // ignore these
    if (opcode.match(/ROUNDABOUT_EXIT|CONTINUE|NONE/)) {
      continue;
    }

    // the image for the turn
    var dirImage = getTurnImage(opcode);
    var dirImageSrc = '';
    if (dirImage !== '') {
      dirImageSrc = '';
    }
    
    // the name that TTS will read out (in blue)
    streetName = getNextStreetName(route.results, i, route.streetNames);

    // roundabouts with nth exit instructions
    if (opcode == 'ROUNDABOUT_ENTER') {
      opcode += route.results[i].instruction.arg + 'th exit';
      opcode = opcode.replace(/1th/, '1st');
      opcode = opcode.replace(/2th/, '2nd');
      opcode = opcode.replace(/3th/, '3rd');
    }

    // convert opcode to pretty text
    opcode = opcode.replace(/APPROACHING_DESTINATION/, 'arrive');
    opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?LEFT/, 'at the roundabout, turn left');
    opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?RIGHT/, 'at the roundabout, turn right');
    opcode = opcode.replace(/ROUNDABOUT_(EXIT_)?STRAIGHT/, 'at the roundabout, continue straight');
    opcode = opcode.replace(/ROUNDABOUT_ENTER/, 'at the roundabout, take ');
    opcode = opcode.toLowerCase().replace(/_/, ' ');
    opcode = opcode.replace(/uturn/, 'make a U-turn');
    opcode = opcode.replace(/roundabout u/, 'at the roundabout, make a U-turn');

    // convert keep to exit if needed
    var keepSide = Waze.model.isLeftHand ? /keep left/ : /keep right/;
    if (opcode.match(keepSide) && i+1 < route.results.length &&
        isKeepForExit(route.results[i].roadType, route.results[i+1].roadType)) {
      opcode = opcode.replace(/keep (.*)/, 'exit $1');
    }

    // show turn symbol on the map (for first route only)
    if (r == 0) {
      if (opcode == 'arrive') {
        var end = nav_json.coords.length - 1;
        var title = 'arrive at ' + (streetName != '' ? streetName : 'destination');
        addTurnImageToMap(nav_json.coords[end], dirImage, title);
      } else {
        //var title = opcode + (streetName != '' ? ' onto ' + streetName : '');
        var title = opcode.replace(/at the roundabout, /, '');
        if (streetName != '') title += ' onto ' + streetName;
        addTurnImageToMap(route.results[i+1].path, dirImage, title);
      }
    }

    // pretty street name
    if (streetName != '') {
      if (opcode == 'arrive') {
        streetName = ' at <span style="color: blue">' + streetName + '</span>';
      } else {
        streetName = ' onto <span style="color: blue">' + streetName + '</span>';
      }
    }
      
    // display new instruction
    currentItem = document.createElement('a');
    currentItem.className = 'step';
    currentItem.innerHTML = getTurnImageSrc(dirImage) + opcode + streetName;
    if (opcode.match(/0th exit/)) {
      currentItem.style.color = 'red';
    }
    instructions.appendChild(currentItem);
  }

  // append distance and time to last instruction
  currentItem.title = (totalDist/1609).toFixed(3) + " miles";
  currentItem.innerHTML += ' - ' + totalDist/1000 + ' km';
  currentItem.innerHTML += ' - ' + timeFromSecs(totalTime);
  //if (detourSaving > 0) {
  //  currentItem.innerHTML += '<br>&nbsp; <i>detour saved ' + timeFromSecs(detourSaving) + '</i>';
  //}
};

function getNextStreetName(results, index, streetNames) {
  var streetName = '';
  var unnamedCount = 0;
  var unnamedLength = 0;

  // destination
  if (index == results.length-1) {
    streetName = streetNames[results[index].street];
    if (!streetName || streetName === null)
      streetName = '';
  }
  
  // look ahead to next street name
  while (++index < results.length && streetName == '') {
    streetName = streetNames[results[index].street];
    if (!streetName || streetName === null)
      streetName = '';

    // "Navigation instructions for unnamed segments" <- in the Wiki
    if (streetName == '' && !isFreewayOrRamp(results[index].roadType) 
		&& !isRoundabout(results[index].path.segmentId)) {
      unnamedLength += length;
      unnamedCount++;
      if (unnamedCount >= 4 || unnamedLength >= 400) {
        //console.log("- unnamed segments too long; break");
        break;
      }
    }
  }
  
  return streetName;
}

function getTurnImage(opcode) {
  var dirImage = '';
  switch (opcode) {
    case "CONTINUE":
    case "NONE":        dirImage = "big_direction_forward"; break;
    case "TURN_LEFT":   dirImage = "big_direction_left"; break;
    case "TURN_RIGHT":  dirImage = "big_direction_right"; break;
    case "KEEP_LEFT":
    case "EXIT_LEFT":   dirImage = "big_direction_exit_left"; break;
    case "KEEP_RIGHT":
    case "EXIT_RIGHT":  dirImage = "big_direction_exit_right"; break;
    case "UTURN":       dirImage = "big_directions_roundabout_u"; break;
    case "APPROACHING_DESTINATION":   dirImage = "big_direction_end"; break;
    case "ROUNDABOUT_LEFT":
    case "ROUNDABOUT_EXIT_LEFT":      dirImage = "big_directions_roundabout_l"; break;
    case "ROUNDABOUT_RIGHT":
    case "ROUNDABOUT_EXIT_RIGHT":     dirImage = "big_directions_roundabout_r"; break;
    case "ROUNDABOUT_STRAIGHT":
    case "ROUNDABOUT_EXIT_STRAIGHT":  dirImage = "big_directions_roundabout_s"; break;
    case "ROUNDABOUT_ENTER":
    case "ROUNDABOUT_EXIT":           dirImage = "big_directions_roundabout"; break;
    case "ROUNDABOUT_U":              dirImage = "big_directions_roundabout_u"; break;
    default: dirImage = '';
  }
  return dirImage;
}

function getTurnImageSrc(dirImage) {
  var imgRoot = '/assets-editor/images/vectors/routeInstructions/';
  if (dirImage != '') {
    return '<img src="' + imgRoot + dirImage + '.png" style="float: left; top: 0; padding-right: 4px" width="16" height="16" />';
  }
  return '';
}

function isKeepForExit(fromType, toType) {
  // primary to non-primary
  if (isPrimaryRoad(fromType) && !isPrimaryRoad(toType)) {
    return true;
  }
  // ramp to non-primary or non-ramp
  if (isRamp(fromType) && !isPrimaryRoad(toType) && !isRamp(toType)) {
    return true;
  }
  return false;
}

function isFreewayOrRamp(t) {
  return t === 3 /*FREEWAY*/ || t === 4 /*RAMP*/;
}

function isPrimaryRoad(t) {
  return t === 3 /*FREEWAY*/ || t === 6 /*MAJOR_HIGHWAY*/ || t === 7 /*MINOR_HIGHWAY*/;
}

function isRamp(t) {
  return t === 4 /*RAMP*/;
}

function isRoundabout(id) {
  segment = Waze.model.segments.get(id);
  if (typeof segment != "undefined") {
    return segment.attributes.junctionId !== null;
  }
  return false;
}

function timeFromSecs(seconds)
{
  var hh = '00'+Math.floor(((seconds/86400)%1)*24);
  var mm = '00'+Math.floor(((seconds/3600)%1)*60);
  var ss = '00'+Math.round(((seconds/60)%1)*60);
  return hh.slice(-2) + ':' + mm.slice(-2) + ':' + ss.slice(-2);
}

function addTurnImageToMap(location, image, title) {
  if (image == '') return;
  
  var coords = OpenLayers.Layer.SphericalMercator.forwardMercator(location.x, location.y);
  var point = new OL.Geometry.Point(coords.lon,coords.lat);
  var imgRoot = '/assets-editor/images/vectors/routeInstructions/';

  var style = {
    externalGraphic: imgRoot + image + ".png",
    graphicWidth: 30,
    graphicHeight: 32,
    graphicZIndex: 9999,
    label: title,
    labelXOffset: 20,
    labelAlign: 'l',
    labelOutlineColor: 'white',
    labelOutlineWidth: 3,
    fontWeight: 'bold',
    fontColor: routeColors[0]
  };
  
  if (title.match(/0th exit/)) {
    style.fontColor = 'red';
  }

  var imageFeature = new OL.Feature.Vector(point, null, style);
  
  // Display new segment
  WMERC_lineLayer_route.addFeatures([imageFeature]);
}

/* helper function */
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 initialiseRouteChecker() {
  if (typeof Waze == 'undefined') {
    return; // not WME
  }

  console.log("WME Route Checker: initialising v" + wmerc_version);
  
  if (localStorage.WMERouteChecker) {
    route_options = JSON.parse(localStorage.WMERouteChecker);
    console.log("WME Route Checker: loaded options: " + route_options);
  }

  /* dirty hack to inject stylesheet in to the DOM */
  var style = document.createElement('style');
  style.innerHTML = "#routeTest {padding: 0 4px 0 0; overflow-y: auto;}\n"
                  + "#routeTest p.route {margin: 0; padding: 4px 8px; border-bottom: silver solid 3px; background: #eee}\n"
                  + "#routeTest a.step {display: block; margin: 0; padding: 3px 8px; text-decoration: none; color:black;border-bottom: silver solid 1px;}\n"
                  + "#routeTest a.step:hover {background: #ffd;}\n"
                  + "#routeTest a.step:active {background: #dfd;}\n"
                  + "#routeTest div.routes_footer {text-align: center; margin-bottom: 25px;}\n";
  (document.body || document.head || document.documentElement).appendChild(style);

  // add a new layer for routes
  WMERC_lineLayer_route = new OL.Layer.Vector("Route Checker Script",
    { rendererOptions: { zIndexing: true },
      shortcutKey: "S+t",
      uniqueName: 'route_checker' }
  );
  Waze.map.addLayer(WMERC_lineLayer_route);
  Waze.map.addControl(new OL.Control.DrawFeature(WMERC_lineLayer_route, OL.Handler.Path));

  // find best ZIndex
  for (var i = 0; i < Waze.map.layers.length; i++) {
    var l = Waze.map.layers[i];
    if (/Waze.Control.SelectHighlightFeature/.test(l.name)) {
      routeZIndex = l.getZIndex()+1;
    }
  }

  // hack in translation:
  I18n.translations[I18n.locale].layers.name.route_checker = "Route Checker Script";

  // ...and then hide it
  $("label:contains('Route Checker Script')").parent().remove();

  // add listener for whenever selection changes
  Waze.selectionManager.events.register("selectionchanged", null, showRouteOptions);

  showRouteOptions(); // for permalinks
}

// bootstrap!
(function()
{
  setTimeout(initialiseRouteChecker, 1003);
})();

/* end ======================================================================= */