WME Route Checker

Allows editors to check the route between two segments

当前为 2020-01-25 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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://beta.waze.com/*
// @exclude             https://www.waze.com/*user/*editor/*
// @version             1.30
// @grant               none
// ==/UserScript==

// globals
var wmerc_version = "1.30";

var AVOID_TOLLS = 1;
var AVOID_FREEWAYS = 2;
var AVOID_DIRT = 4;
var ALLOW_UTURNS = 16;
var VEHICLE_TAXI = 64;
var VEHICLE_BIKE = 128;

var route_options = ALLOW_UTURNS; // default

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

var WMERC_lineLayer_route;

function showRouteOptions() {
  if (W.selectionManager.getSelectedFeatures().length != 2) {
    WMERC_lineLayer_route.destroyFeatures();
    WMERC_lineLayer_route.setVisibility(false);
    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 navTabs = getElementsByClassName('nav-tabs', segmentBox)[0];

  var routeTab = document.createElement('li');
  routeTab.innerHTML = '<a href="#route-checker" data-toggle="tab" id="route-tab-link">Routes</a>';
  navTabs.appendChild(routeTab);
  getId('route-tab-link').onclick = fetchRoute;

  var tabContent = getElementsByClassName('tab-content', segmentBox)[0];

  var routeContent = document.createElement('div');
  routeContent.id = 'route-checker';
  routeContent.className = 'tab-pane';
  tabContent.appendChild(routeContent);

  // add new edit tab to left of the map
  var routeOptions = document.createElement('div');
  routeOptions.id = "routeOptions";
  routeOptions.style.borderTop = "solid 2px #E9E9E9";
  routeOptions.style.borderBottom = "solid 2px #E9E9E9";
  routeContent.appendChild(routeOptions);

  var lang = I18n.translations[I18n.locale];

  if (location.hostname.match(/editor.*.waze.com/)) {
    var coords1 = getCoords(W.selectionManager.getSelectedFeatures()[0]);
    var coords2 = getCoords(W.selectionManager.getSelectedFeatures()[1]);
    var url = getLivemap()
            + `&from_lon=${coords1.lon}&from_lat=${coords1.lat}`
            + `&to_lon=${coords2.lon}&to_lat=${coords2.lat}`;

    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>';
  } else {
    routeOptions.innerHTML = `<p><b><a href="#" id="goroutes" title="WME Route Checker v${wmerc_version}" style="color:#8309e1">`
                    + 'Show routes between these 2 segments</a> &raquo;</b><br>'
                    + '<b>'+lang.restrictions.editing.driving.dropdowns.vehicle_type+':</b>'
                    + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_private" value="0" checked> '
                    + lang.restrictions.vehicle_types.PRIVATE + '</span>'
                    + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_taxi" value="1"> '
                    + lang.restrictions.vehicle_types.TAXI + '</span>'
                    + ' <span style="white-space: nowrap;"><input type="radio" name="_vehicleType" id="_vehicleType_bike" value="1"> '
                    + lang.restrictions.vehicle_types.MOTORCYCLE + '</span>'
                    + '<br>'
                    + '<b>Avoid:</b>'
                    + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidTolls" /> ' + lang.edit.segment.fields.toll_road + '</span>'
                    + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidFreeways" /> ' + lang.segment.road_types[3] + '</span>'
                    + ' <span style="white-space: nowrap;"><input type="checkbox" id="_avoidDirt" /> ' + lang.edit.segment.fields.unpaved + '</span>'
                    + '<br>'
                    + '<b>Allow:</b>'
                    + ' <input type="checkbox" id="_allowUTurns" /> U-Turns</p>';

     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('_vehicleType_taxi').checked        = route_options & VEHICLE_TAXI;
     getId('_vehicleType_bike').checked        = route_options & VEHICLE_BIKE;

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

  // create empty div ready for instructions
  var routeTest = document.createElement('div');
  routeTest.id = "routeTest";
  routeContent.appendChild(routeTest);

  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('_vehicleType_taxi').checked ? VEHICLE_TAXI : 0)
                + (getId('_vehicleType_bike').checked ? VEHICLE_BIKE : 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 OL.Layer.SphericalMercator.inverseMercator(seglon,seglat);
}

function fetchRoute(reverse) {
  saveOptions();

  var coords1, coords2;
  reverse = (reverse !== false);
  var selected = W.selectionManager.getSelectedFeatures();
  if (reverse) {
    coords1 = getCoords(selected[0]);
    coords2 = getCoords(selected[1]);
  } else {
    coords1 = getCoords(selected[1]);
    coords2 = getCoords(selected[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: 'HISTORIC_TIME',
    clientVersion: '4.0.0',
    timeout: 60000,
    nPaths: 3,
    options: getOptions()};

  if (route_options & VEHICLE_TAXI) {
    data.vehicleType = 'TAXI';
  }
  else if (route_options & VEHICLE_BIKE) {
    data.vehicleType = 'MOTORCYCLE';
  }
  //data.id = "beta";

  $.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 OL.LonLat(W.map.getCenter().lon,W.map.getCenter().lat);
  center_lonlat.transform(new OL.Projection ("EPSG:900913"),new OL.Projection("EPSG:4326"));
  var coords = `?lon=${center_lonlat.lon}&lat=${center_lonlat.lat}`;

  if (route_options & VEHICLE_TAXI) {
    coords += "&rp_vehicleType=TAXI";
  }
  else if (route_options & VEHICLE_BIKE) {
    coords += "&rp_vehicleType=MOTORCYCLE";
  }
  coords += "&rp_options=" + getOptions();

  return `https://www.waze.com/livemap${coords}&overlay=false`;
}

function getRoutingManager() {
  if (W.model.countries.getObjectById(235) || W.model.countries.getObjectById(40)) { // US & Canada
    return '/RoutingManager/routingRequest';
  } else if (W.model.countries.getObjectById(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 = OL.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) {
  WMERC_lineLayer_route.destroyFeatures();
  WMERC_lineLayer_route.setVisibility(true);

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

  // write instructions
  var 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(instructions, nav_json.alternatives[r], r);
      plotRoute(nav_json.alternatives[r].coords, r);
    }
    nav_coords = nav_json.alternatives[0].coords;
  } else {
    showInstructions(instructions, nav_json, 0);
    plotRoute(nav_json.coords, 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 = '&#8646; 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=ll.${lat1},${lon1}`
          + `&to=ll.${lat2},${lon2}`;

  // 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(instructions, 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], getTurnImage('FORWARD'), departFrom);
  }

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

  var segments = [];
  // 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;

    segments.push(route.results[i].path.segmentId);

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

    // ignore these
    if (opcode.match(/ROUNDABOUT_EXIT|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 = W.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) {
	  var title;
      if (opcode == 'arrive') {
        var end = nav_json.coords.length - 1;
        title = 'arrive at ' + (streetName !== '' ? streetName : 'destination');
        addTurnImageToMap(nav_json.coords[end], dirImage, title);
      } else {
        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>';
  //}

  var selectAll = document.createElement('a');
  selectAll.className = 'step select';
  selectAll.innerHTML = 'Select route segments &#8605;';
  selectAll.href = "#";
  selectAll.addEventListener('click', function() { selectSegmentIDs(segments); }, false);
  instructions.appendChild(selectAll);
}

function selectSegmentIDs(segments) {
  var objects = [];
  for (var i = 0; i < segments.length; i++) {
	var segment = W.model.segments.objects[segments[i]];
	if (typeof segment != 'undefined') {
	  objects.push(segment);
	}
  }
  W.selectionManager.setSelectedModels(objects);
  return false;
}

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 = '';
  if (W.model.getTopCountry().leftHandTraffic) {
    opcode = opcode.replace(/(ROUNDABOUT_)(EXIT_)?/, "$1UK_");
  }
  switch (opcode) {
    case "CONTINUE":
    case "NONE":        dirImage = "big_direction_forwardc0958c4d4c5c79bcb656d34f3afb3ea2"; break;
    case "TURN_LEFT":   dirImage = "big_direction_left5b94fa33f945d46ab1bdd1131ac0457e"; break;
    case "TURN_RIGHT":  dirImage = "big_direction_right2d403871f04763260a40c537e231897e"; break;
    case "KEEP_LEFT":
    case "EXIT_LEFT":   dirImage = "big_direction_exit_left1c1498a6dec9582bae81d34ec9e6dc3b"; break;
    case "KEEP_RIGHT":
    case "EXIT_RIGHT":  dirImage = "big_direction_exit_rightba4fee1380f556a8570252c6745f1442"; break;
    case "UTURN":       dirImage = "big_direction_u63cf785b68a57e8663020098cd07ed76"; break;
    case "APPROACHING_DESTINATION":   dirImage = "big_direction_end25226c71aed0efd3a2db41978066febc"; break;
    case "ROUNDABOUT_LEFT":
    case "ROUNDABOUT_EXIT_LEFT":      dirImage = "big_directions_roundabout_l54dc48b91e36549b26bae30135462780"; break;
    case "ROUNDABOUT_UK_LEFT":        dirImage = "big_directions_roundabout_UK_ldc86a0b99cfcd4ed03b0192d5b350c70"; break;
    case "ROUNDABOUT_RIGHT":
    case "ROUNDABOUT_EXIT_RIGHT":     dirImage = "big_directions_roundabout_rc114740b6cafc42177a53aa6c803c14d"; break;
    case "ROUNDABOUT_UK_RIGHT":       dirImage = "big_directions_roundabout_r_UKc34794c4d01ec8a9fa012150d2f1e02a"; break;
    case "ROUNDABOUT_STRAIGHT":
    case "ROUNDABOUT_EXIT_STRAIGHT":  dirImage = "big_directions_roundabout_sffadf4fd7b277b8ef2f21688e79b9351"; break;
    case "ROUNDABOUT_UK_STRAIGHT":    dirImage = "big_directions_roundabout_UK_s01ea5c47f4e08b20532505d84b3271e0"; break;
    case "ROUNDABOUT_ENTER":
    case "ROUNDABOUT_EXIT":           dirImage = "big_directions_roundabout9f9bf37022d431be50fecc457cd6e3df"; break;
    case "ROUNDABOUT_UK_ENTER":
    case "ROUNDABOUT_UK_EXIT":        dirImage = "big_directions_roundabout_UK7dce607d7359326a799fd9d3ad8542aa"; break;
    case "ROUNDABOUT_U":              dirImage = "big_directions_roundabout_u3634283a7d740f30eb18c203f6a357be"; break;
    case "ROUNDABOUT_UK_U":           dirImage = "big_directions_roundabout_u_UKba204c8a12885976f9bc5b07165b8644"; break;
    default: dirImage = '';
  }
  return dirImage;
}

function getTurnImageSrc(dirImage) {
  var imgRoot = 'https://editor-assets.waze.com/production/img/';
  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) {
  var segment = W.model.segments.getObjectById(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 = OL.Layer.SphericalMercator.forwardMercator(location.x, location.y);
  var point = new OL.Geometry.Point(coords.lon,coords.lat);
  var imgRoot = 'https://editor-assets.waze.com/production/img/';

  var style = {
    externalGraphic: imgRoot + image + ".png",
    graphicWidth: 30,
    graphicHeight: 32,
    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 W == '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 a.select {color: #00f; text-align: right}\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",
    { displayInLayerSwitcher: false,
      uniqueName: 'route_checker' }
  );
  W.map.addLayer(WMERC_lineLayer_route);

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

  showRouteOptions(); // for permalinks
}

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

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