WME Color Highlights

Adds colours to road segments to show their status

当前为 2014-07-10 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name                WME Color Highlights
// @namespace           http://userscripts.org/users/419370
// @description         Adds colours to road segments to show their status
// @include             https://www.waze.com/editor/*
// @include             https://www.waze.com/*/editor/*
// @include             https://editor-beta.waze.com/*
// @version             1.91
// @grant               none
// ==/UserScript==

var wmech_version = "1.91"

/* bootstrap, will call initialiseHighlights() */
function bootstrapHighlights()
{
  var bGreasemonkeyServiceDefined = false;

  try {
    bGreasemonkeyServiceDefined = (typeof Components.interfaces.gmIGreasemonkeyService === "object");
  }
  catch (err) { /* Ignore */ }

  if (typeof unsafeWindow === "undefined" || ! bGreasemonkeyServiceDefined) {
    unsafeWindow    = ( function () {
      var dummyElem = document.createElement('p');
      dummyElem.setAttribute('onclick', 'return window;');
      return dummyElem.onclick();
    }) ();
  }

  /* begin running the code! */
  setTimeout(initialiseHighlights, 999);
}

// set of 'bad names' to flag on UK roads only
// if editing for another country, search for 234
var badNames = [
   ' (Av|Avenue)$',
   ' Ch$',                    // chase
   ' (Crt|Court|Close|Cls)$',
   ' (Cres|Crescent|Crs)$',
   ' (Drv|Drive)$',
   ' (Gardens?|Grdns?)$',     // optional s
   ' (Grn|Green|Grove)$',
   ' (Grv|Grove|Place)$',
   ' (Lane|Road|Street)$',
   ' (Te|Tce|Terr|Terrace)$',
   ' (Wk|Wy)$',               // Walk|Way

   '[.]$',                    // anything ending in .
   '^[a-z]+| [a-z]{3}',       // missing capitals
   ' By-[Pp]ass',             // Bypass
   '^([AB]\\d+ - )?St ',      // Saint (St.)
   '^[AB]\\d+ : ',            // Colon instead of dash
   '^[AB]\\d+(  |-| -?[A-Za-z])', // double or missing space, or missing hyphen
   ' \\([AB]\\d+\\)$'         // ending in (A123)
];
var reBadNames = new RegExp(badNames.join('|'), '');

var goodNames = [
   '^([AB]\\d+.* - )?The (Avenue|Boulevard|Broadway|Bypass|Circus|Close|Court|Crescent|Drive)$',
   '^([AB]\\d+.* - )?The (Gardens?|Green|Groves?|Lane|Mount|Place|Park|Ridge|Square|Street|Terrace|Valley)$'
];
var reGoodNames = new RegExp(goodNames.join('|'), '');


/* =========================================================================== */
function highlightSegments() {
  var showLocked = getId('_cbHighlightLocked').checked;
  var showToll   = getId('_cbHighlightToll').checked;
  var showNoCity = getId('_cbHighlightNoCity').checked;
  var showNoName = getId('_cbHighlightUnnamed').checked;
  var showOneWay = getId('_cbHighlightOneWay').checked;
  var showNoDirection = getId('_cbHighlightNoDirection').checked;
  var showRestrictions = getId('_cbHighlightRestrictions').checked;
  var specificCity = getId('_cbHighlightCity').checked;
  var specificCityInvert = getId('_cbHighlightCityInvert').checked;
  var specificRoadType = getId('_cbHighlightRoadType').checked;
  var showNoTerm = getId('_cbHighlightNoTerm').checked;

  var showRecent = advancedMode ? getId('_cbHighlightRecent').checked : false;
  var specificEditor = advancedMode ? getId('_cbHighlightEditor').checked : false;
  //var showHouseNums = advancedMode ? getId('_cbHighlightHouseNums').checked : false;

  var showPlaces = getId('_cbHighlightPlaces').checked;

  var tNow = new Date();
  var numDays;
  var selectedUserId = null;
  var selectedCityId = null;

  if (specificEditor) {
    var selectUser = getId('_selectUser');
    if (selectUser.selectedIndex >= 0)
      selectedUserId = selectUser.options[selectUser.selectedIndex].value;
    else
      specificEditor = false;
  }

  if (specificCity) {
    var selectCity = getId('_selectCity');
    if (selectCity.selectedIndex >= 0)
      selectedCityId = selectCity.options[selectCity.selectedIndex].value;
    else
      specificCity = false;
  }

  if (specificRoadType) {
    var selectRoadType = getId('_selectRoadType');
    if (selectRoadType.selectedIndex >= 0)
      selectedRoadType = selectRoadType.options[selectRoadType.selectedIndex].value;
    else
      specificRoadType = false;
  }

  if (showRecent) {
    numDays = getId('_numRecentDays').value;
    if (numDays === undefined) numDays = 0;
  }

  // counters
  var numUserHighlighted = 0;
  var numCityHighlighted = 0;

  for (var seg in Waze.model.segments.objects) {
    var segment = Waze.model.segments.get(seg);
    var attributes = segment.attributes;
    var line = getId(segment.geometry.id);

    if (line !== null) {
      var sid = attributes.primaryStreetID;

      // check that WME hasn't highlighted this segment
      var opacity = line.getAttribute("stroke-opacity");
      var lineWidth = line.getAttribute("stroke-width");
      if (opacity == 1 || lineWidth == 9)
        continue;

      // turn off highlights when roads are no longer visible
      var roadType = attributes.roadType;
      if (Waze.map.zoom <= 3 && (roadType < 2 || roadType > 7) ) {
        if (opacity > 0.1) {
          line.setAttribute("stroke","#dd7700");
          line.setAttribute("stroke-opacity",0.001);
          line.setAttribute("stroke-dasharray", "none");
        }
        continue;
      }

      // highlight all newly paved roads (or roads without any nodes)
      if (sid === null || (attributes.toNodeID === null && attributes.fromNodeID === null && roadType < 9)) {
        if (opacity < 0.1) {
          line.setAttribute("stroke","#f00");
          line.setAttribute("stroke-opacity",0.75);
          line.setAttribute("stroke-width", 10);
        }
        continue;
      }
      var street = Waze.model.streets.get(sid);

      // get attributes for this segment
      var toll        = attributes.fwdToll;
      var locked      = attributes.lockRank > 0;
      var ranked      = attributes.rank > 0;
      var noEdit      = attributes.permissions == 0;
      var noName      = (street != null) && street.isEmpty;
      var badName     = !noName && street.name.match(reBadNames) && !street.name.match(reGoodNames);
      var cityID      = (street != null) && street.cityID;
      var noCity      = false;
      var countryID   = 0;
      if (cityID != null && Waze.model.cities.get(cityID) != null) {
        noCity = Waze.model.cities.get(cityID).isEmpty;
        countryID = Waze.model.cities.get(cityID).countryID;
      }
      var oneWay      = ((attributes.fwdDirection + attributes.revDirection) == 1); // it is 1-way only if either is true
      var noDirection = (!attributes.fwdDirection && !attributes.revDirection); // Could use the .attribute.allowNoDirection?
      var hasRestrictions = (attributes.fwdRestrictions.length + attributes.revRestrictions.length > 0);
      var updatedOn   = new Date(attributes.updatedOn);
      var updatedBy   = attributes.updatedBy;
      var roundabout  = attributes.junctionID !== null;
      var tDif = (tNow.getTime() - updatedOn.getTime()) / 86400000;

      // get current state of the line
      var lineColor = line.getAttribute("stroke");

      // default colours
      var newColor = "#dd7700";
      var newOpacity = 0.001;
      var newDashes = "none";
      var newWidth = 8;

      // Recent Edits within X days, with decaying green opacity
      if (showRecent && numDays >= 0 && tDif <= numDays) {
        if ((updatedBy == selectedUserId) || (!specificEditor)) {
          var heatScale = 0.75 / numDays;
          newColor = "#0f0";
          newOpacity = Math.min(0.999999, 1 - (tDif * heatScale));
        }
      }

      // Toll = Dashed
      else if (toll && showToll) {
        newColor = "#00f";
        newOpacity = 0.5;
        newDashes = "10 10";
      }

      // No Edit = Black
      else if (noEdit && showLocked) {
        newColor = "#000";
        newOpacity = 0.75;
        newWidth = 3;
      }

      // Locked = Red
      else if (locked && showLocked) {
        newColor = "#f00";
        newWidth = 6;
        newOpacity = 0.2 * Math.min(5, attributes.lockRank);
      }

      else if (ranked && showLocked) {
        newColor = "#f00";
        newWidth = 6;
        newDashes = "2 8";
        newOpacity = 0.2 * Math.min(5, attributes.rank);
      }

      else if (hasRestrictions && showRestrictions) {
        newColor = "#909";
        newDashes = "10 10";
        newOpacity = 0.5;
      }

      // bad roundabouts = Lime
      //else if (roundabout && (!oneWay || !noName) && showNoTerm) {
      //  newColor = "#BE0";
      //  newOpacity = 0.5;
      //}

      // Unnamed (No Name) = Orange
      // except roundabouts and non-Streets
      else if (noName && showNoName && !roundabout && attributes.roadType < 8) {
        newColor = "#fb0";
        newOpacity = 0.5;
      }

      // bad names (UK only) = Orange dashed
      else if ((badName || !noName && roundabout) && showNoName && countryID == 234 && attributes.roadType != 4) {
        newColor = "#fb0";
        newOpacity = 0.5;
        newDashes = "10 10";
        newWidth = 6;
      }

      // No City = Gray
      else if (noCity && showNoCity) {
        newColor = "#888";
        newOpacity = 0.5;
      }

      // No Direction = Cyan
      else if (noDirection && showNoDirection) {
        newColor = "#0ff";
        newOpacity = 0.3;
      }

      // One Way = Blue
      else if (oneWay && showOneWay) {
        newColor = "#00f";
        newOpacity = 0.2;
      }

      // Unterminated segments: no end node or not connected = Lime
      else if (showNoTerm && (attributes.toNodeID === null || attributes.fromNodeID === null || attributes.toNodeID === attributes.fromNodeID) && attributes.length > 10) {
        newColor = "#BE0";
        newOpacity = 0.5;
      }

      //else if (showHouseNums && attributes.hasHNs) {
      //  newColor = "#4E4";
      //  newOpacity = 0.5;
      //}

      // selected road type = purple
      else if (specificRoadType && attributes.roadType == selectedRoadType) {
        newColor = "#909";
        newOpacity = 0.5;
        newWidth = 4;
      }

      // special road types: non-drivable / non-routable
      else if (specificRoadType && selectedRoadType == 98 && nonRoutableTypes.contains(attributes.roadType)) {
        newColor = "#909";
        newOpacity = 0.5;
        newWidth = 4;
      }
      else if (specificRoadType && selectedRoadType == 99 && nonDrivableTypes.contains(attributes.roadType)) {
        newColor = "#909";
        newOpacity = 0.5;
        newWidth = 4;
      }


      // highlight segments by selected user, unless already highlighted
      if (specificEditor && !showRecent) {
        if (updatedBy == selectedUserId && newColor == "#dd7700") {
          newColor = "#00ff00";
          newOpacity = 0.5;
          numUserHighlighted++;
        } else if (updatedBy != selectedUserId) {
          newColor = "#dd7700";
          newOpacity = 0.001;
          newDashes = "none";
        }
      }

      // highlight segments by selected City, unless already highlighted
      // if city is only on an alternate street highlight it with dashes
      if (specificCity) {
        var altCityMatch = false;
        var specificCityMatch = (cityID == selectedCityId);
        if (specificCityInvert)
          specificCityMatch = (cityID != selectedCityId && !noCity);

        if (!specificCityMatch) {
          // look for matching city in alternate streets
          for (var i in attributes.streetIDs) {
            var streetID = attributes.streetIDs[i];
            var currentStreet = Waze.model.streets.get(streetID);
            if (currentStreet == null)
              continue;
            var match = (currentStreet.cityID == selectedCityId);
            if (specificCityInvert)
              match = !match
            if (match) {
              altCityMatch = true;
              break;
            }
          }
        }

        if (specificCityMatch && (newColor == "#dd7700" || newColor == "#888")) {
          newColor = "#ff0";
          newOpacity = 0.75;
          newDashes = "none";
          numCityHighlighted++;
        } else if (altCityMatch && (newColor == "#dd7700" || newColor == "#888")) {
          newColor = "#ffff01";
          newOpacity = 0.75;
          newDashes = "10 10";
          newWidth = 6;
          numCityHighlighted++;
        } else if (!specificCityMatch && !altCityMatch && !noCity) {
          newColor = "#dd7700";
          newOpacity = 0.001;
          newDashes = "none";
        }
      }

      // highlight railroads (as Places)
      if (showPlaces && attributes.roadType == 18) {
          newColor = "#444";
          newOpacity = 0.8;
          newDashes = "4 12";
          newWidth = 3;
      }

      // if colour has changed, update the line attributes
      if (lineColor != newColor) {
        line.setAttribute("stroke", newColor);
        line.setAttribute("stroke-opacity", newOpacity);
        line.setAttribute("stroke-dasharray", newDashes);
        if (newColor != "#dd7700") { //default
          line.setAttribute("stroke-width", newWidth);
        } else {
          line.setAttribute("stroke-width", 6);
        }
      }
    } // end if (line !== null)
  } // end of loop

  numUserHighlightedText = getId('_numUserHighlighted');
  if (specificEditor)
    numUserHighlightedText.innerHTML = ' = ' + numUserHighlighted;
  else
    numUserHighlightedText.innerHTML = '';

  numCityHighlightedText = getId('_numCityHighlighted');
  if (specificCity)
    numCityHighlightedText.innerHTML = ' = ' + numCityHighlighted;
  else
    numCityHighlightedText.innerHTML = '';
} // end of function

function highlightPlaces() {
  if (typeof Waze.model.venues == "undefined") {
    return;
  }

  // if checkbox unticked, reset places to original style
  if (!getId('_cbHighlightPlaces').checked) {
    for (var mark in Waze.model.venues.objects) {
      var venue = Waze.model.venues.get(mark);
      var poly = getId(venue.geometry.id);
      if (poly !== null) {
        if (poly.getAttribute("fill-opacity") != 0.3) {
          poly.setAttribute("fill","#d191d6");
          poly.setAttribute("stroke","#d191d6");
          poly.setAttribute("fill-opacity",0.3);
          poly.setAttribute("stroke-opacity",1);
        }
      }
    }
    return;
  }


  var specificCity = getId('_cbHighlightCity').checked;
  var specificCityInvert = getId('_cbHighlightCityInvert').checked;

  if (specificCity) {
    var selectCity = getId('_selectCity');
    if (selectCity.selectedIndex >= 0) {
      selectedCityId = selectCity.options[selectCity.selectedIndex].value;
    } else
      specificCity = false;
  }

  for (var mark in Waze.model.venues.objects) {
    var venue = Waze.model.venues.get(mark);
    var poly = getId(venue.geometry.id);
    if (poly !== null && venue.geometry.toString().match(/^POLYGON/)) {
      venueStreet = Waze.model.streets.get(venue.attributes.streetID);
      selectedCityMatch = (specificCity && venueStreet.cityID == selectedCityId);
      if (specificCityInvert) selectedCityMatch = !selectedCityMatch;

      // highlight places which have the City field set in the address = yellow
      if (poly.getAttribute("fill") != "#ff8") {
        if (specificCity && selectedCityMatch) {
          poly.setAttribute("fill","#ff8");
          poly.setAttribute("stroke","#cc0");
        }
      }
      // if was yellow and now not yellow, reset
      else if (!specificCity || !selectedCityMatch) {
        poly.setAttribute("fill","#d191d6");
        poly.setAttribute("stroke","#d191d6");
      }

      if (poly.getAttribute("fill") == "#d191d6" && poly.getAttribute("fill-opacity") == 0.3) {
        var categories   = venue.attributes.categories;

        // gas station = orange
        if (categories.indexOf("GAS_STATION") > -1) {
          poly.setAttribute("fill","#f90");
          poly.setAttribute("stroke","#f90");
        }

        // parking lot = cyan
        else if (categories.indexOf("PARKING_LOT") > -1) {
          poly.setAttribute("fill","#099");
          poly.setAttribute("stroke","#0cc");
        }

        // water = blue
        else if (categories.indexOf("RIVER_STREAM") > -1 ||
                 categories.indexOf("SEA_LAKE_POOL") > -1) {
          poly.setAttribute("fill","#09f");
          poly.setAttribute("stroke","#06c");
        }

        // park/grass/trees = green
        else if (categories.indexOf("PARK") > -1 ||
                 categories.indexOf("FARM") > -1 ||
                 categories.indexOf("FOREST_GROVE") > -1 ||
                 categories.indexOf("GOLF_COURSE") > -1) {
          poly.setAttribute("fill","#4f4");
          poly.setAttribute("stroke","#6a6");
        }
        
        poly.setAttribute("fill-opacity",0.28);
      }
    }
  }
}

function highlightSelectedNodes() {
  if (Waze.map.zoom <= 3 || !advancedMode)
    return true;

  var showTurns = advancedMode ? getId('_cbHighlightTurns').checked : false;

  // look for a selected node or segment (there can only be 1)
  var currentNodes = [];
  var selectType = '';
  if (Waze.selectionManager.selectedItems.length == 1) {
    var selection = Waze.selectionManager.selectedItems[0];
    if (selection !== null) {
      if (selection.state == "Update" && lastModified == false) {
        lastSelected = null;
        lastModified = true;
      } else if (selection.state != "Update" && lastModified == true) {
        lastSelected = null;
        lastModified = false;
      }

      selectType = selection.type;
      if (selection.type == "node") {
         currentNodes.push(selection.fid);
      }

      else if (selection.type == "segment") {
        currentNodes.push(selection.attributes.fromNodeID);
        currentNodes.push(selection.attributes.toNodeID);
      }
    }
  }

  // selection changed, then reset previous highlights | selectType == 'node'
  if ((lastSelected != currentNodes[0] || !showTurns) && selectedLines.length > 0) {
    for (var i = 0; i < selectedLines.length; i++) {
      line = getId(selectedLines[i]);
      if (line !== null && line.getAttribute("stroke-width") == 8)
        line.setAttribute("stroke-opacity", 0.0001);
    }
    selectedLines = [];
  }

  // no node selected, quit now
  if (currentNodes.length === 0) {
    lastSelected = null;
    lastModified = false;
    return true;
  }

  var numSoftTurns = 0;
  var numRevConns = 0;
  var numUTurns = 0;
  var showWarning = false;

  for (var i = 0; i < currentNodes.length; i++) {
    var currentNode = currentNodes[i];

    var node = Waze.model.nodes.get(currentNode);
    if (node === undefined) continue;

    // ignore dead-end nodes
    if (node.attributes.segIDs.length <= 1) {
      lastSelected = currentNode[0];
      continue;
    }

    // find segments that connect to this node
    for (var j = 0; j < node.attributes.segIDs.length; j++) {
      var segID = node.attributes.segIDs[j];
      var segment = Waze.model.segments.get(segID);
      if (segment === undefined)
        continue;

      var attributes = segment.attributes;
      var line = getId(segment.geometry.id);
      if (line === null || line === undefined)
        continue;

      var newColor = null;
      var n = (attributes.fromNodeID == currentNode) ? 1 : 0; // n = 0 for fwd, 1 for rev

      // check for soft-turns
      var nodeLocked = (n == 0) ? attributes.fwdTurnsLocked : attributes.revTurnsLocked;
      if (nodeLocked === false) {
        newColor = "#ff0"; // yellow
        numSoftTurns++;
      }

      // highlight reverse connectivity and nodes that are causing it in purple
      if (attributes.fwdDirection == true && attributes.revDirection == false) {
        // one way A->B
        for (var key in node.attributes.connections) {
          var val = node.attributes.connections[key];
          var connSegs = key.split(",");
          var checkseg = (n == 0) ? connSegs[1] : connSegs[0];
          if (val == true && checkseg == segID) {
            newColor = "#f0f";
            numRevConns++;
          }
        }
      }

      if (attributes.fwdDirection == false && attributes.revDirection == true) {
        // one way A<-B
        for (var key in node.attributes.connections) {
          var val = node.attributes.connections[key];
          var connSegs = key.split(",");
          var checkseg = (n == 0) ? connSegs[0] : connSegs[1];
          if (val == true && checkseg == segID) {
            newColor = "#f0f";
            numRevConns++;
          }
        }
      }

      if (attributes.fwdDirection == true && attributes.revDirection == true) {
        // two-way, check for connections back to the same segment
        if (node.attributes.segIDs.length > 1) {
          for (var key in node.attributes.connections) {
            var val = node.attributes.connections[key];
            var connSegs = key.split(",");
            if (val == true && connSegs[0] == segID && connSegs[1] == segID) {
              newColor = "#0ff";
              numUTurns++;
            }
          }
        }
      }

      var oldOpacity = line.getAttribute("stroke-opacity");
      if (newColor !== null && oldOpacity < 1 && showTurns) {
        line.setAttribute("stroke", newColor);
        line.setAttribute("stroke-opacity", 1);
        line.setAttribute("stroke-width", 8);
        line.setAttribute("stroke-dasharray", "none");
        selectedLines.push(segment.geometry.id);
      }
      if (newColor !== null)
        showWarning = true;
    }
  }

  if (currentNodes.length > 0) {
    var currentNode = currentNodes[0];

    if (showWarning === true) {
      // setup new box for warnings about this node
      var selectionBox;
      if (selectType == 'segment')
        selectionBox = getId('segment-edit-general');
      else
        selectionBox = getId('node-edit-general');

      var nodeDetails = getId('nodeTipsBox');
      if (!nodeDetails) {
        nodeDetails = document.createElement('div');
        nodeDetails.id = 'nodeTipsBox';
        selectionBox.appendChild(nodeDetails);
      } else {
        return;
        nodeDetails.innerHTML = '';
      }

      if (numSoftTurns > 0) {
        nodeText = document.createElement('div');
        if (selectType == 'node')
          nodeText.title = "Press Q and then re-enable each allowed turn.";
        else
          nodeText.title = "Select individual nodes to see details.";
        nodeText.style.padding = "4px 2px";
        nodeText.innerHTML = "<b>Warning:</b> " + selectType + " has " + numSoftTurns + " soft turns.";
        nodeText.style.backgroundColor = '#ffc';
        nodeDetails.appendChild(nodeText);
      }
      if (numRevConns > 0) {
        nodeText = document.createElement('div');
        if (selectType == 'node')
          nodeText.title = "Press Q and then re-enable each allowed turn.";
        else
          nodeText.title = "Select individual nodes to see details.";
        nodeText.style.padding = "4px 2px";
        nodeText.innerHTML = "<b>Warning:</b> " + selectType + " has " + numRevConns + " reverse connections.";
        nodeText.style.backgroundColor = '#fcf';
        nodeDetails.appendChild(nodeText);
      }
      if (numUTurns > 0) {
        nodeText = document.createElement('div');
        nodeText.title = "Use the U-Turn arrows to set when a U-Turn is allowed.";
        nodeText.style.padding = "4px 2px";
        nodeText.innerHTML = "<b>Notice:</b> " + selectType + " has " + numUTurns + " U-Turns.";
        nodeText.style.backgroundColor = '#cff';
        nodeDetails.appendChild(nodeText);
      }

      if (selectType == 'node') {
        nodeText = document.createElement('div');
        nodeText.style.padding = "4px 2px";
        nodeText.innerHTML = '<br>';
        if (typeof unsafeWindow.WME_JNF_Version != "undefined") {
            nodeText.innerHTML += '<span style="float:right">[<a href="http://userscripts.org/scripts/show/144939" target="_blank">JNF '+unsafeWindow.WME_JNF_Version+'</a>]</span>';
          if (typeof unsafeWindow.WME_JNF_FixNode == "function") {
            nodeText.innerHTML += '<i><a href="#" id="_autoFixLink">'
                               +  'Automatically fix this node</a></i> (Q)<br>';
          } else {
            nodeText.innerHTML += "<i style='color: red'>JNF script not active. Upgrade!</i><br>";
          }
        } else {
          nodeText.innerHTML += '<i>Hover over text above for help.</i><br>';
        }
        nodeText.innerHTML += '<i>For a full explanation, <a href="http://waze.cryosphere.co.uk/node-help" target="_blank">see this page</a>.</i><br>';
        nodeDetails.appendChild(nodeText);

        autoFixLink = getId('_autoFixLink');
        if (autoFixLink != null) {
          autoFixLink.onclick = autoFixNode;
        }
      }
    }
  }

  lastSelected = currentNodes[0];
  return true;
}

function autoFixNode()
{
  currentNode = Waze.selectionManager.selectedItems[0];
  unsafeWindow.WME_JNF_FixNode(currentNode);

  return false;
}

/* display average speed for selected segments (code by bgodette) */
/* no longer used */
function extraDetails() {
  if (!advancedMode)
    return;

  if (Waze.selectionManager.selectedItems.length == 1) {
    var selection = Waze.selectionManager.selectedItems[0];
    if (selection) {
      if (selection.type == "segment") {
        segeditgen = document.getElementById("segment-edit-general");
        segextra = document.getElementById("segment-extra-details");
        if (!segextra) {
          segextra = document.createElement('div');
          segextra.id = 'segment-extra-details';
          segeditgen.appendChild(segextra);
        }
        var fwdSpeed = selection.attributes.fwdCrossSpeed;
        var revSpeed = selection.attributes.revCrossSpeed;
        var editTime = new Date(selection.attributes.updatedOn);

        segextra.innerHTML = "<br><u title=\"*Additional data displayed by Color Highlights Script\">Average speed for segment</u>*<br>"
                           + "&nbsp; A->B: " + fwdSpeed + "km/h (" + Math.round(fwdSpeed * 6.21371) / 10 + "mph)<br>"
                           + "&nbsp; B->A: " + revSpeed + "km/h (" + Math.round(revSpeed * 6.21371) / 10 + "mph)<br>"
                           + "Segment Rank: " + (selection.attributes.rank + 1) + " cones<br>"
                           + "<i>" + editTime.toTimeString() + "</i>";
      }
    }
  }
  return true;
}

function highlightBadNodes() {
  if (Waze.map.zoom <= 3 || !advancedMode)
    return true;

  var showTurns = advancedMode ? getId('_cbHighlightTurns').checked : false;
  var showRestrictions = getId('_cbHighlightRestrictions').checked;

  for (var currentNode in Waze.model.nodes.objects) {
    var node = Waze.model.nodes.get(currentNode);
    if (node === undefined) continue;

    var numRestrictions = 0;
    var numSoftTurns = 0;
    var numRevConns = 0;
    var numUTurns = 0;

    // ignore dead-end nodes
    if (node.attributes.segIDs.length <= 1) {
      continue;
    }

    // find segments that connect to this node
    for (var j = 0; j < node.attributes.segIDs.length; j++) {
      var segID = node.attributes.segIDs[j];
      var segment = Waze.model.segments.get(segID);
      if (segment === undefined)
        continue;

      var attributes = segment.attributes;

      // count restictions
      if (showRestrictions) {
        for (var key in node.attributes.restrictions) {
          if (key) numRestrictions++;
        }
      }

      if (attributes.roadType < 2 || attributes.roadType > 7)
        continue; // only look at Primary Streets and above

      var n = (attributes.fromNodeID == currentNode) ? 1 : 0; // n = 0 for fwd, 1 for rev

      // check for soft-turns
      var nodeLocked = (n == 0) ? attributes.fwdTurnsLocked : attributes.revTurnsLocked;
      if (nodeLocked === false) {
        numSoftTurns++;
      }

      // highlight reverse connectivity and nodes that are causing it in purple
      if (attributes.fwdDirection == true && attributes.revDirection == false) {
        // one way A->B
        for (var key in node.attributes.connections) {
          var val = node.attributes.connections[key];
          var connSegs = key.split(",");
          var checkseg = (n == 0) ? connSegs[1] : connSegs[0];
          if (val == true && checkseg == segID) {
            numRevConns++;
          }
        }
      }

      if (attributes.fwdDirection == false && attributes.revDirection == true) {
        // one way A<-B
        for (var key in node.attributes.connections) {
          var val = node.attributes.connections[key];
          var connSegs = key.split(",");
          var checkseg = (n == 0) ? connSegs[0] : connSegs[1];
          if (val == true && checkseg == segID) {
            numRevConns++;
          }
        }
      }

      if (attributes.fwdDirection == true && attributes.revDirection == true) {
        // two-way, check for connections back to the same segment
        if (node.attributes.segIDs.length > 1) {
          for (var key in node.attributes.connections) {
            var val = node.attributes.connections[key];
            var connSegs = key.split(",");
            if (val == true && connSegs[0] == segID && connSegs[1] == segID) {
              numUTurns++;
            }
          }
        }
      }
    }

    var newColor = null;
    if (numUTurns > 0)          newColor = "#0ff";
    else if (numRevConns > 0)   newColor = "#f0f";
    else if (numRestrictions > 0)   newColor = "#909";

    var circle = getId(node.geometry.id);
    if (newColor != null && circle != null) {
      opacity = circle.getAttribute("fill-opacity");
      if (opacity < 0.1) {
        circle.setAttribute("fill-opacity", 0.75);
        circle.setAttribute("fill", newColor);
      }
    }
  }

  return true;
}


// add logged in user to drop-down list
function initUserList() {
  if (!advancedMode) return;

  var thisUser = Waze.loginManager.user;
  if (thisUser === null) return;

  var selectUser = getId('_selectUser');
  var usrOption = document.createElement('option');
  var usrRank = thisUser.normalizedLevel;
  var usrText = document.createTextNode(thisUser.userName + " (" + usrRank + ")");
  usrOption.setAttribute('value',thisUser.id);
  usrOption.appendChild(usrText);
  selectUser.appendChild(usrOption);
  console.log("WME Highlights: Init User list: " + thisUser.userName);
}

// add current city in to drop-down list
function initCityList() {
  var locationInfo = Waze.map.getControlsByClass('Waze.Control.LocationInfo')[0];
  if (locationInfo === null || locationInfo.location === null)
    return;

  var cityName = locationInfo.location.city;
  var thisCity = null;
  for (var city in Waze.model.cities.objects) {
    var cityObj = Waze.model.cities.get(city);
    if (cityObj.name == cityName) {
      thisCity = cityObj.id;
      break;
    }
  }
  if (thisCity === null)
    return;

  var selectCity = getId('_selectCity');
  var cityOption = document.createElement('option');
  var cityText = document.createTextNode(cityName);
  cityOption.appendChild(cityText);
  cityOption.setAttribute('value',thisCity);
  selectCity.appendChild(cityOption);
  console.log("WME Highlights: Init City list: " + cityName);

  // stop listening for this event
  Waze.model.events.unregister("mergeend", null, initCityList);
}

// populate drop-down list of editors
function updateUserList() {
  // update list of users - for high zoom and AMs only
  if (Waze.map.zoom <= 3 || !advancedMode)
    return;

  var selectUser = getId('_selectUser');
  var numUsers = Waze.model.users.objects.length;
  if (numUsers === 0)
    return;

  // preserve current selection
  var currentId = null;
  if (selectUser.selectedIndex >= 0)
    currentId = selectUser.options[selectUser.selectedIndex].value;

  // collect array of users who have edited segments
  var editorIds = [];
  for (var seg in Waze.model.segments.objects) {
    var segment = Waze.model.segments.get(seg);
    if (getId(segment.geometry.id) === null)
      continue;
    var updatedBy = segment.attributes.updatedBy;
    if (editorIds.indexOf(updatedBy) == -1)
      editorIds.push(updatedBy);
  }
  editorIds.sort();

  if (editorIds.length === 0)
    return;

  // reset list
  selectUser.options.length = 0;

  // add all users in field of view
  for (var i = 0; i < editorIds.length; i++) {
    var id = editorIds[i];
    var user = Waze.model.users.get(id);
    if (user === null || typeof(user) === "undefined")
      continue;

    var usrOption = document.createElement('option');
    var usrRank = user.normalizedLevel;
    var usrText = document.createTextNode(user.userName + " (" + usrRank + ")");
    if (currentId !== null && id == currentId)
      usrOption.setAttribute('selected',true);
    usrOption.setAttribute('value',id);
    usrOption.appendChild(usrText);
    selectUser.appendChild(usrOption);
  }
}

// populate drop-down list of Cities
function updateCityList() {
  var selectCity = getId('_selectCity');
  var numCities = Waze.model.cities.objects.length;

  if (numCities === 0)
    return;

  // preserve current selection
  var currentId = null;
  if (selectCity.selectedIndex >= 0)
    currentId = selectCity.options[selectCity.selectedIndex].value;

  // collect array of Cities
  var cityIds = [];
  var cityObjs = [];

  //=========================================================================================
  // This new block of code checks the following assumed conditions:
  // * Every U.S. city should have an associated state
  // * Every 'No city' U.S. city should be properly numbered (not an orphan blank city)
  // * We only care about states if get.cities shows us close enough to the U.S. to matter
  // * Any non US's city state code should be 99 (None/other)
  //========================================================================================

  // collect list if unique cities from the segments
  for (var sid in Waze.model.streets.objects) {
    var cid = Waze.model.streets.get(sid).cityID;
    var city = Waze.model.cities.get(cid);
    if (cityIds.indexOf(cid) == -1) {
      cityIds.push(cid);
      cityObjs.push({id: city.id, name: city.name, state: city.stateID, country: city.countryID});
    }
  }

  if (cityIds.length === 0)
    return;

  // reset list
  selectCity.options.length = 0;

  // count how many (non empty) states there are here
  var numStates = 0
  for (var obj in Waze.model.states.objects) {
    state = Waze.model.states.get(obj);
    if (state.id != 1 && state.name != "")
      numStates++;
  }

  // count how many countries there are here
  var numCountries = 0;
  for (var obj in Waze.model.countries.objects) {
    numCountries++;
  }

  // add all cities in field of view
  cityObjs.sort(function(a,b){return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0;});
  for (var i = 0; i < cityObjs.length; i++) {
    var cityID = cityObjs[i].id;
    // "State-like CityIDs" to ignore. These are consistently over 100,000,000.
    if (cityID > 100000000) continue;
    var cityName  = cityObjs[i].name;
    var stateID   = cityObjs[i].state;
    var countryID = cityObjs[i].country;

    if (countryID == 235) {  // for U.S. only
      // 'No City' segments in the U.S. should have an assigned state.
      // This ID has a prescribed range. If not in this range, we get 'other' state pollution in map,
      // or a bogus blank city associated to the state.

      if (cityName === "") {
        if (cityID >= 999900 && cityID <= 999999) {
          cityName = "No City";
        } else {
          cityName = "EMPTY CITY";
        }
      }
    }

    else { // for non U.S. segments
      if (cityName === "") cityName = "No City";
    }

    var stateObj = Waze.model.states.get(stateID);
    var countryObj = Waze.model.countries.get(countryID);

    // State handling. All cities should have an associated state. Throw an error if not.
    if (numStates > 0) {
      // If more than one state, we're appending it. No brainer.
      if (numStates > 1) {
        // ... and, if one of those states is 'Other', that's an error. Report it.
        if (stateObj.id === 99) {
            cityName += ", " + "NO STATE";
        }
        // If we get here, the state ID should be fine. Append it.
        else {
          cityName += ", " + stateObj.name;
        }
      }

      // If we have more than one country and are in the US, append the state for sanity.
      if (numStates == 1 && numCountries > 1) {
        cityName += ", " + stateObj.name;
      }
    }

    // If we're on a non-US street, state should always be 99, 'Other/none'.
    // Append if this is the case. Otherwise don't add anything.
    else if (stateID != 99 && stateID > 1) {
      cityName += ", INVALID STATE";
    }

    if (numCountries > 1) {
      cityName += ", " + countryObj.name.replace('United States', 'U.S.');
    }

    // create option in select menu
    var cityOption = document.createElement('option');
    var cityText = document.createTextNode(cityName);

    if (currentId !== null && cityID == currentId)
      cityOption.setAttribute('selected',true);
    cityOption.setAttribute('value',cityID);
    cityOption.appendChild(cityText);
    selectCity.appendChild(cityOption);
  }
}

var RoadTypes = {
  1: "Streets",
 21: "Service Road",
 98: "Non-Routable Roads",    // --------------
108: "- Dirt roads",
120: "- Parking Lot Road",
117: "- Private Road",
199: "Non-Drivable Roads",    // --------------
210: "- Pedestrian Bw",
205: "- Walking Trails",
216: "- Stairway",
219: "- Runway/Taxiway"
//  2: "Primary Street",
//  3: "Freeways",
//  4: "Ramps",
//  6: "Major Highway",
//  7: "Minor Highway",
// 18: "Railroad",
};

var nonRoutableTypes = new Array(8, 20, 17);
var nonDrivableTypes = new Array(10, 5, 16, 19);

// populate drop-down list of editors
function populateRoadTypes() {
  var selectRoadType = getId('_selectRoadType');

  for (var id in RoadTypes) {
    var type = RoadTypes[id]
    var usrOption = document.createElement('option');
    var usrText = document.createTextNode(type);
    if (id == 1)
      usrOption.setAttribute('selected',true);
    usrOption.setAttribute('value',id % 100);
    usrOption.appendChild(usrText);
    selectRoadType.appendChild(usrOption);
  }
}

function toggleOptions () {
  var objStyle = getId('hiliteOptions').style.display;
  if  (objStyle == "none") {
    objStyle = "block";
    getId('_btnHide').innerHTML = "hide";
  }
  else {
    objStyle = "none";
    getId('_btnHide').innerHTML = "show";
  }
  getId('hiliteOptions').style.display = objStyle;
  if (advancedMode)
    getId('advancedOptions').style.display = objStyle;
  getId('hilitePlaces').style.display = objStyle;
  return false;
}

// enable advanced options if user is logged in and at least an AM
function enableAdvancedOptions()
{
  if (advancedMode) return;

  if (typeof Waze == 'undefined')
    Waze = unsafeWindow.Waze;

  if (typeof Waze.loginManager == 'undefined')
    Waze.loginManager = unsafeWindow.Waze.loginManager;

  if (typeof Waze.loginManager == 'undefined')
    Waze.loginManager = unsafeWindow.loginManager;

  if (Waze.loginManager !== null && Waze.loginManager.isLoggedIn()) {
    thisUser = Waze.loginManager.user;
    if (thisUser !== null && thisUser.normalizedLevel >= 3) {
      Waze.loginManager.events.unregister("afterloginchanged", null, enableAdvancedOptions);
      console.log('WME Highlights: Advanced Options enabled for ' + thisUser.userName);
      getId('advancedOptions').style.display = 'block';
      advancedMode = true;
      initUserList();
      initCityList();
    }
  }
}

/* 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 initialiseHighlights()
{
  // global variables
  advancedMode = false;
  betaMode = location.hostname.match(/editor-beta.waze.com/);
  lastSelected = null;
  lastModified = false;
  selectedLines = [];

  // add new box to left of the map
  var addon = document.createElement('section');
  addon.id = "highlight-addon";

  if (navigator.userAgent.match(/Chrome/)) {
    addon.innerHTML  = '<b><a href="https://chrome.google.com/webstore/detail/wme-color-highlights/ijnldkoicbhinlgnoigchihmegdjobjc" target="_blank"><u>'
                     + 'WME Colour Highlights</u></a></b> &nbsp; v' + wmech_version;
  } else {
    addon.innerHTML  = '<b><a href="http://userscripts.org/scripts/show/141050" target="_blank"><u>'
                     + 'WME Colour Highlights</u></a></b> &nbsp; v' + wmech_version;
                     + ' <a href="http://userscripts.org/scripts/show/141050" target="_blank">'
                     + '<img src="http://waze.cryosphere.co.uk/scripts/update.php?version=' + wmech_version + '" /></a>';
  }

  // highlight segements
  var section = document.createElement('p');
  section.style.paddingTop = "8px";
  section.style.textIndent = "16px";
  section.id = "hiliteOptions";
  section.innerHTML  = '<b>Highlight Segments</b><br>'
                     + '<input type="checkbox" id="_cbHighlightLocked" /> <span title="Dotted = Automatic Locks (if available)">Locks*</span> (Red)<br>'
                     + '<input type="checkbox" id="_cbHighlightToll" /> Toll (Dashed)<br>'
                     + '<input type="checkbox" id="_cbHighlightUnnamed" /> No Name (Orange)<br>'
                     + '<input type="checkbox" id="_cbHighlightNoCity" /> No City (Gray)<br>'
                     + '<input type="checkbox" id="_cbHighlightOneWay" /> One Way (Blue)<br>'
                     + '<input type="checkbox" id="_cbHighlightNoDirection" /> Unknown Direction (Cyan)<br>'
                     + '<input type="checkbox" id="_cbHighlightRestrictions" /> Time/Vehicle Restrictions (Purple)<br>'
                     + '<input type="checkbox" id="_cbHighlightNoTerm" /> <span title="*Dead-end roads should have terminating nodes on the end, or Waze cannot route to or from them.">Unterminated Roads* (Lime)</span><br>'
                     + '<input type="checkbox" id="_cbHighlightCity" /> Filter by City (Yellow) '
                     + '<input type="checkbox" id="_cbHighlightCityInvert" /> invert <br> '
                     + '  <select id="_selectCity" name="_selectCity" style="margin: 0 0 4px 16px"></select>'
                     + '<span id="_numCityHighlighted"></span><br>'
                     + '<input type="checkbox" id="_cbHighlightRoadType" /> Highlight a Road Type (Purple)<br> '
                     + '  <select id="_selectRoadType" name="_selectRoadType" style="margin: 0 0 4px 16px"></select><br>'
                     ;
  addon.appendChild(section);

  // advanced options
  section = document.createElement('p');
  section.style.paddingTop = "8px";
  section.style.textIndent = "16px";
  section.style.display = "none";
  section.id = 'advancedOptions';
  section.innerHTML  = '<b>Advanced Options</b><br>'
             + '<input type="checkbox" id="_cbHighlightRecent" /> Recently Edited (Green)<br> '
             + '  <input type="number" min="0" max="365" size="3" id="_numRecentDays"  style="margin: 0 0 4px 16px"/> days<br>'
             + '<input type="checkbox" id="_cbHighlightEditor" /> Filter by Editor (Green)<br> '
             + '  <select id="_selectUser" name="_selectUser" style="margin: 0 0 4px 16px"></select>'
             + '<span id="_numUserHighlighted"></span><br>'
             + '<input type="checkbox" id="_cbHighlightTurns" /> <span title="*Highlight turn errors when a segment or node is selected, and on mjor roads">Turn Warnings for Selected Nodes*</span><br>'
             //'<input type="checkbox" id="_cbHighlightHouseNums" /> <span>Segments with House Numbers</span><br>'
             ;
  addon.appendChild(section);

  // highlight places
  section = document.createElement('p');
  section.style.padding = "8px 16px";
  section.style.textIndent = "-16px";
  section.id = "hilitePlaces";
  section.innerHTML  = '<input type="checkbox" id="_cbHighlightPlaces" /> <b title="'
                     + 'parks/trees = green, water = blue, parking lot = cyan, '
                     + 'everything else = pink">Highlight Places</b><br>';
  addon.appendChild(section);

  var userTabs = getId('user-info');
  var navTabs = getElementsByClassName('nav-tabs', userTabs)[0];
  var tabContent = getElementsByClassName('tab-content', userTabs)[0];

  newtab = document.createElement('li');
  newtab.innerHTML = '<a href="#sidepanel-highlights" data-toggle="tab">Highlight</a>';
  navTabs.appendChild(newtab);

  addon.id = "sidepanel-highlights";
  addon.className = "tab-pane";
  tabContent.appendChild(addon);

  // check for AM or CM, and unhide Advanced options
  enableAdvancedOptions();

  // always populate road types
  populateRoadTypes();

  // setup onclick handlers for instant update:
  getId('_cbHighlightLocked').onclick = highlightSegments;
  getId('_cbHighlightToll').onclick = highlightSegments;
  getId('_cbHighlightUnnamed').onclick = highlightSegments;
  getId('_cbHighlightNoCity').onclick = highlightSegments;
  getId('_cbHighlightOneWay').onclick = highlightSegments;
  getId('_cbHighlightNoDirection').onclick = highlightSegments;
  getId('_cbHighlightRestrictions').onclick = highlightSegments;
    

  getId('_cbHighlightRecent').onclick = highlightSegments;
  getId('_cbHighlightEditor').onclick = highlightSegments;
  getId('_cbHighlightCity').onclick = highlightSegments;
  getId('_cbHighlightCityInvert').onclick = highlightSegments;
  getId('_cbHighlightRoadType').onclick = highlightSegments;
  getId('_cbHighlightNoTerm').onclick = highlightSegments;
  getId('_cbHighlightTurns').onclick = highlightSelectedNodes;
  //getId('_cbHighlightHouseNums').onclick = highlightSegments;

  getId('_selectUser').onfocus = updateUserList;
  getId('_selectUser').onchange = highlightSegments;

  getId('_selectCity').onfocus = updateCityList;
  getId('_selectCity').onchange = highlightSegments;

  getId('_selectRoadType').onchange = highlightSegments;

  getId('_cbHighlightPlaces').onclick = highlightPlaces;


  // restore saved settings
  if (localStorage.WMEHighlightScript) {
    console.log("WME Highlights: loading options");
    options = JSON.parse(localStorage.WMEHighlightScript);

    getId('_cbHighlightLocked').checked       = options[1];
    getId('_cbHighlightToll').checked         = options[2];
    getId('_cbHighlightUnnamed').checked      = options[3];
    getId('_cbHighlightNoCity').checked       = options[4];
    getId('_cbHighlightOneWay').checked       = options[5];
    getId('_cbHighlightNoDirection').checked  = options[6];
    getId('_cbHighlightNoTerm').checked       = options[14];
    getId('_cbHighlightCity').checked         = options[15];
    getId('_cbHighlightRoadType').checked     = options[16];
    getId('_selectRoadType').selectedIndex    = options[17];
    getId('_cbHighlightPlaces').checked    = options[7];
    getId('_cbHighlightRestrictions').checked = options[19];
    
    if (options[12] === undefined) options[12] = 7;
    getId('_cbHighlightRecent').checked   = options[11];
    getId('_numRecentDays').value         = options[12];
    getId('_cbHighlightEditor').checked   = options[13];
    getId('_cbHighlightTurns').checked    = options[18];
    //getId('_cbHighlightHouseNums').checked= options[19];
  } else {
    getId('_cbHighlightPlaces').checked = true;
    getId('_cbHighlightTurns').checked    = true;
  }

  if (typeof Waze.model.venues == "undefined") {
    getId('_cbHighlightPlaces').checked = false;
    getId('_cbHighlightPlaces').disabled = true;
  }

  // overload the WME exit function
  saveHighlightOptions = function() {
    if (localStorage) {
      console.log("WME Highlights: saving options");
      var options = [];

      // preserve previous options which may get lost after logout
      if (localStorage.WMEHighlightScript)
        options = JSON.parse(localStorage.WMEHighlightScript);

      options[1] = getId('_cbHighlightLocked').checked;
      options[2] = getId('_cbHighlightToll').checked;
      options[3] = getId('_cbHighlightUnnamed').checked;
      options[4] = getId('_cbHighlightNoCity').checked;
      options[5] = getId('_cbHighlightOneWay').checked;
      options[6] = getId('_cbHighlightNoDirection').checked;
      options[14] = getId('_cbHighlightNoTerm').checked;
      options[15] = getId('_cbHighlightCity').checked;
      options[16] = getId('_cbHighlightRoadType').checked;
      options[17] = getId('_selectRoadType').selectedIndex;
      options[7] = getId('_cbHighlightPlaces').checked;
      options[19] = getId('_cbHighlightRestrictions').checked;
     
      if (advancedMode) {
        options[11] = getId('_cbHighlightRecent').checked;
        options[12] = getId('_numRecentDays').value;
        options[13] = getId('_cbHighlightEditor').checked;
        options[18] = getId('_cbHighlightTurns').checked;
        //options[19] = getId('_cbHighlightHouseNums').checked;
      }

      localStorage.WMEHighlightScript = JSON.stringify(options);
    }
  }
  window.addEventListener("beforeunload", saveHighlightOptions, false);

  // begin periodic updates
  window.setInterval(highlightSegments,333);
  window.setInterval(highlightPlaces,500);
  window.setInterval(highlightBadNodes,444);
  window.setInterval(highlightSelectedNodes,250);

  // trigger code when page is fully loaded, to catch any missing bits
  window.addEventListener("load", function(e) {
    var mapProblems = getId('map-problems-explanation')
    if (mapProblems !== null) mapProblems.style.display = "none";
    enableAdvancedOptions();
  });

  // register some events...
  Waze.map.events.register("zoomend", null, highlightSegments);
  Waze.map.events.register("zoomend", null, highlightPlaces);
  Waze.map.events.register("zoomend", null, highlightSelectedNodes);

  Waze.selectionManager.events.register("selectionchanged", null, highlightSelectedNodes);
  //Waze.selectionManager.events.register("selectionchanged", null, extraDetails);

  Waze.loginManager.events.register("afterloginchanged", null, enableAdvancedOptions);
  Waze.model.events.register("mergeend", null, initCityList);
}

/* engage! =================================================================== */
bootstrapHighlights();

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