Greasy Fork is available in English.

WME Show Alt Names

Shows alt names for selected segments

Per 25-05-2015. Zie de nieuwste versie.

/* global wLib */
/* global W */
/* global $ */
/* global jQuery */
/* global OL */

// ==UserScript==
// @name            WME Show Alt Names
// @description     Shows alt names for selected segments
// @version         0.40
// @author          SAR85
// @copyright       SAR85
// @license         CC BY-NC-ND
// @grant           none
// @include         https://www.waze.com/editor/*
// @include         https://www.waze.com/*/editor/*
// @include         https://editor-beta.waze.com/*
// @namespace       https://greasyfork.org/users/9321
// @require			https://greasyfork.org/scripts/9794-wlib/code/wLib.js?version=52323
// ==/UserScript==

var altLayer, $altDiv, betaEditor, highlightLayer, $altTable,
	nameArray = [], alternateObjectsArray = [], selectedSegments = [];

/**
 * Returns a random color in rgba() notation.
 * @param {Number} opacity The desirected opacity (a value of the color).
 * returns {String} The rgba() color.
 */
function randomRgbaColor(opacity) {
	opacity = opacity || 0.8;
	function random255() {
		return Math.floor(Math.random() * 255);
	}
	return 'rgba(' + random255() + ',' + random255() + ',' + random255() + ',' + opacity + ')';
}

/**
 * Resets the renderIntent of all features on altLayer.
 * @param {String} intent Optional parameter specifying the intent. The default
 * parameter is 'default'.
 */
function resetRenderIntent(intent) {
    var i, n;
    intent = intent || 'default';
    for (i = 0, n = altLayer.features.length; i < n; i++) {
        altLayer.features[i].renderIntent = intent;
    }
}

/**
 * Pans the map to a segment specified by its ID.
 * @param {Number} id The ID of the segment.
 */
function panToSegment(id) {
	var segment = id && W.model.segments.get(id);
	return segment && W.map.moveTo(segment.geometry.getBounds().getCenterLonLat());
}

/**
 * Selects a segment specified by its ID.
 * @param {Number} id The ID of the segment.
 */
function selectSegment(id) {
	var seg = id && W.model.segments.get(id);
	return seg && W.selectionManager.select([seg]);
}

/** Event handler for changing the highlight color for a segment.
 * @param {Event} event
 */
function changeHighlightColor(event) {
	var i, n, $this = $(this);
	for (i = 0, n = nameArray.length; i < n; i++) {
		if (nameArray[i].name === $this.text()) {
			nameArray[i].color = randomRgbaColor();
			colorTable();
			$this.trigger('mouseenter', { singleSegment: false });
			break;
		}
	}
}

/**
 * Lookup function for the display color of a specified road type. Will return
 * the color for the experimental layer if it is activated, otherwise it will 
 * return the color for the old roads layer.
 * @param {Number} type The roadType to look up.
 * @returns {Object} Object of form {typeString: 'RoadTypeName', typeColor: '#FFFFFF'}, 
 * where RoadTypeName is an abbreviated form of the name of the road type and the type 
 * color is the hex value of the display color.
 */
function getRoadColor(type) {
    var roadTypes = {
		1: { name: 'St', color: '#FFFFFF', expColor: '#FFFFDD' },
		2: { name: 'PS', color: '#CBA12E', expColor: '#FDFAA7' },
		3: { name: 'Fwy', color: '#387FB8', expColor: '#6870C3' },
		4: { name: 'Rmp', color: '#8FB838', expColor: '#B3BFB3' },
		5: { name: 'Trl', color: '#E6E6E6', expColor: '#B0A790' },
		6: { name: 'MH', color: '#C13040', expColor: '#469FBB' },
		7: { name: 'mH', color: '#ECE589', expColor: '#69BF88' },
		8: { name: 'Dirt', color: '#E6E6E6', expColor: '#867342' },
		10: { name: 'Bdwk', color: '#E6E6E6', expColor: '#9A9A9A' },
		16: { name: 'Stwy', color: '#E6E6E6', expColor: '#9A9A9A' },
		17: { name: 'Pvt', color: '#E6E6E6', expColor: '#BEBA6C' },
		18: { name: 'RR', color: '#E6E6E6', expColor: '#B2B6B4' },
		19: { name: 'Rwy', color: '#E6E6E6', expColor: '#222222' },
		20: { name: 'PLR', color: '#E6E6E6', expColor: '#ABABAB' }
		//add ferry
	},
		roadExpName = betaEditor ? 'Roads' : 'Roads experimental',
        roadsExpLayerVisible = W.map.getLayersByName(roadExpName)[0].getVisibility();
    if (type && undefined !== typeof roadTypes[type]) {
        return {
            typeString: roadTypes[type].name,
            typeColor: roadsExpLayerVisible ? roadTypes[type].expColor : roadTypes[type].color
		};
    } else {
        return { typeString: 'error', typeColor: roadTypes[1] };
    }
}

/**
 * Data structure for segment information used to build highlight layer features and
 * the alternate names table.
 * @class
 * @param {Waze.Feature.Vector.Segment} The segment feature on which to base the new instance.
 */
function Alternate(baseFeature) {
	var i, n, street, city;
	if (!baseFeature) {
		return;
	}

	this.attributes = baseFeature.model.attributes;
	this.segmentID = this.attributes.id;
	
	// Make a feature for highlighting on the map.
	this.layerFeature = new OL.Feature.Vector(baseFeature.geometry.clone(), this.attributes);
	
	// Store segment name information.
	street = W.model.streets.get(this.attributes.primaryStreetID);
	city = street && W.model.cities.get(street.cityID);
	this.primaryName = street ? street.name || 'No name' : 'error';
	this.primaryCity = city ? city.name || 'No city' : 'error';

	this.alternates = [];
	for (i = 0, n = this.attributes.streetIDs.length; i < n; i++) {
		street = W.model.streets.get(this.attributes.streetIDs[i]);
		city = street && W.model.cities.get(street.cityID);
		this.alternates.push({
			name: street ? street.name || 'No name' : 'error',
			city: city ? city.name || 'No city' : 'error'
		});
	}

	// Make a table row for displaying segment data.
	this.tableRow = this.createTableRow();
	
	// Add name info to attributes of layer feature and add the feature to the layer.
	// (For compatibility with highlighting functions--old method)
	this.layerFeature.attributes.alt = this.alternates;
	this.layerFeature.attributes.primary = { name: this.primaryName, city: this.primaryCity };
}
Alternate.prototype = /** @lends Alternate.prototype */ {
	createTableRow: function () {
		var i, n, $row, $cell, roadType;

		$row = $('<tr/>').attr('id', 'alt' + this.segmentID);
		
		//add road type to row
		roadType = getRoadColor(this.layerFeature.attributes.roadType);
		$cell = $('<td/>')
			.css('border-right', 'none')
			.append($('<div/>')
			.addClass('altTable-roadType')
			.css('background-color', roadType.typeColor)
			.text(roadType.typeString))
			.append($('<div/>')
			.css({ 'text-align': 'center', 'font-size': '0.8em' })
			.text(this.layerFeature.attributes.length + ' m')
			);
		$row.append($cell);
        
		//add id to row
		$cell = $('<td/>')
			.addClass('altTable-id').css('border-left', 'none')
			.append($('<div/>')
			.text(this.segmentID));
		$row.append($cell);
		
		//add primary name and city to row
		$cell = $('<td/>').addClass('altTable-primary')
			.append($('<div/>')
			.addClass('altTable-primary-name')
			.text(this.primaryName))
			.append($('<div/>')
			.addClass('altTable-primary-city')
			.text(this.primaryCity)
			);
		$row.append($cell);
		
		//add alt names and cities to row
		for (i = 0, n = this.alternates.length; i < n; i++) {
			$cell = $('<td/>').addClass('altTable-alt')
				.append($('<div/>').addClass('altTable-alt-name').text(this.alternates[i].name))
				.append($('<div/>').addClass('altTable-alt-city').text(this.alternates[i].city)
				);
			$row.append($cell);
		}
		return $row;
	}
};

/**
 * Colors the table cells based on segment/city name.
 */
function colorTable() {
	'use strict';
    var i,
		n,
		$table = $('#altTable');

    $table.find('.altTable-primary, .altTable-alt').each(function (index1) {
		var $this = $(this),
			text = $this.text(),
			match = false,
			color;

		for (i = 0, n = nameArray.length; i < n; i++) {
			if (nameArray[i].name === text) {
				$this.css('background-color', nameArray[i].color);
				match = true;
				break;
			}
		}
		if (match === false) {
			color = randomRgbaColor();
			$this.css('background-color', color);
			nameArray.push({ name: text, color: color });
		}
		match = false;
	});
}

/**
 * Populates the table with segment information
 * @param {Number} maxAlternates The maxiumum number of alternates
 * @param {Boolean} sortByNode Whether to sort the table in driving
 * order by node ID.
 * a segment has (how many "Alt" columns are needed).
 */
function populateTable(maxAlternates, sortByNode) {
	'use strict';
	var i, n, j, m, $row;
	
	// Empty table contents.
	$altTable.find('tbody').empty();
	$('.altTable-header-alt').remove();
	
	// Sort if needed.
	if (sortByNode) {
		sortSegmentsByNode();
	}
	
	// Add table rows for each segment.
	for (i = 0, n = selectedSegments.length; i < n; i++) {
		$row = selectedSegments[i].tableRow.clone();
		for (j = selectedSegments[i].alternates.length, m = maxAlternates; j < m; j++) {
			$row.append($('<td/>').addClass('altTable-placeholder'));
		}
		$altTable.append($row);
	}
	
	// Add column headings for alt names.
	for (i = 1, n = maxAlternates; i <= n; i++) {
		$('#altTable-header').append($('<th/>')
			.addClass('altTable-header-alt')
			.text('Alt ' + i));
	}
	
	colorTable();
}

/**
 * Callback for hovering over segment name in the table that
 * colors all features on the altLayer with the same name/city as the
 * one being hovered over.
 * @callback
 * @param {String} nameToMatch The name/city combination to match.
 * @param {String} color The rgba-formatted color value.
 */
function colorFeatures(nameToMatch, color) {
    'use strict';
    var i,
		n,
		j,
		t,
		colorValues,
		feature,
		names,
		nameCityCombined;
    
    //remove opacity from color so it can be controlled by layer style
    colorValues = color.match(/\d+/g);
    color = 'rgb(' + colorValues[0] + ',' + colorValues[1] + ',' + colorValues[2] + ')';

    for (i = 0, n = altLayer.features.length; i < n; i++) {
        feature = altLayer.features[i];
        //combine primary and alt names in one array
        names = feature.attributes.alt.concat(feature.attributes.primary);
        //test names for match
        for (j = 0, t = names.length; j < t; j++) {
            //combine street and city name (as in text of table cell)
            nameCityCombined = names[j].name + names[j].city;
            if (nameCityCombined === nameToMatch) {
                feature.attributes.bgColor = color;
                feature.renderIntent = 'highlight';
            }
        }
    }
}

/**
 * Callback for hovering over a segment ID in the table. Highlights the corresponding
 * altLayer feature black or as specified.
 * @callback
 * @param {Number} id The segment ID to highlight.
 * @param {String} color The rgba-formatted color (optional--default is black).
 */
function colorSegment(id, color) {
    'use strict';
    var i, n, feature;
    color = color || 'rgba(0, 0, 0, 0.8)';
    for (i = 0, n = altLayer.features.length; i < n; i++) {
        feature = altLayer.features[i];
        if (feature.attributes.id == id) {
            feature.attributes.bgColor = color;
            feature.renderIntent = 'highlight';
            break;
        }
    }
}

/**
 * Handles table events for hovering and calls appropriate function
 * for highlighting.
 * @callback
 * @param {Event} event The event object.
 */
function applyHighlighting(event) {
    var $this1;
    switch (event.type) {
        case 'mouseenter':
            $this1 = $(this);
            if (event.data.singleSegment) {
                colorSegment($this1.text());
            } else {
                colorFeatures($this1.text(), $this1.css('background-color'));
            }
            $('#altTable tbody td').each(function (index) {
				var $this2 = $(this);
				if ($this1.text() === $this2.text()) {
					$this2.parent().addClass('altTable-selected');
				}
            });
            break;
        case 'mouseleave':
            resetRenderIntent();
            $('#altTable tr').each(function (index) {
                $(this).removeClass('altTable-selected');
            });
            break;
    }
    altLayer.redraw();
}

/**
 * Event handler for selection events. Checks for appropriate condions
 * for running script, creates Alternate objects as necessary, displays/hides 
 * UI elements.
 * @callback
 */
function checkSelection() {
	var i, n, j, m, alternate, thisItem, maxAlternates = 0, selectedItems;
	selectedSegments = [];
    $('#altAutoSelect').hide();
	if (W.selectionManager.hasSelectedItems() && altLayer.getVisibility()) {
		selectedItems = W.selectionManager.selectedItems;
		if (selectedItems.length > 1) {
			$('#altAutoSelect').show();
		}
		for (i = 0, n = selectedItems.length; i < n; i++) {
			thisItem = selectedItems[i];
			if (thisItem.model.type === 'segment') {
				for (j = 0, m = alternateObjectsArray.length; j < m; j++) {
					if (alternateObjectsArray[j].segmentID === thisItem.model.attributes.id) {
						if (alternateObjectsArray[j].layerFeature.attributes.updatedOn !==
							thisItem.model.attributes.updatedOn) {
							alternateObjectsArray.splice(j, 1);
						} else {
							alternate = alternateObjectsArray[j];
							continue;
						}
					}
				}
				if (!alternate) {
					alternate = new Alternate(thisItem);
					alternateObjectsArray.push(alternate);
				}
				selectedSegments.push(alternate);
				altLayer.addFeatures(alternate.layerFeature);
				if (maxAlternates < alternate.alternates.length) {
					maxAlternates = alternate.alternates.length;
					selectedSegments.maxAlternates = maxAlternates;
				}
				alternate = null;
			}
		}
		populateTable(maxAlternates, $('#altSortByNode').prop('checked'));
		$altDiv.fadeIn();
	} else {
		$altDiv.fadeOut();
		altLayer.removeAllFeatures();
		if (alternateObjectsArray.length > 50) {
			alternateObjectsArray = [];
		}
	}
}

/**
 * Checks all segments in WME for alt names and adds feature for
 * highlighting.
 */
function checkAllSegments() {
    'use strict';
    var segID, segment;
    highlightLayer.removeAllFeatures();
    if (altLayer.getVisibility() && $('#altHighlights').prop('checked')) {
        for (segID in W.model.segments.objects) {
            segment = W.model.segments.objects[segID];
            if (W.model.segments.objects.hasOwnProperty(segID) &&
                segment.attributes.streetIDs.length > 0) {
				highlightLayer.addFeatures(new OL.Feature.Vector(segment.geometry.clone()));
            }
        }
    }
}

/**
 * Sorts the selected segments in "driving order" starting with first selected based on Node ID.
 */
function sortSegmentsByNode() {
	'use strict';
	var reversedSegments = W.selectionManager.getReversedSegments();
	
	// Check each selected segment to see if it is reversed
	selectedSegments.forEach(function (item, index, array) {
		item.reversed = reversedSegments[item.segmentID];
	});

	// Sort the segments in place based on the connecting node IDs.
	selectedSegments.sort(function (segment1, segment2) {
		if (segment1 === selectedSegments[0]) {
			if (segment1.attributes.toNodeID === (segment2.reversed ?
				segment2.attributes.toNodeID : segment2.attributes.fromNodeID) ||
				segment1.attributes.fromNodeID === (segment2.reversed ?
				segment2.attributes.fromNodeID : segment2.attributes.toNodeID)) {
				return -1;
			} else {
				return 0;
			}
		} else if ((segment1.reversed ? segment1.attributes.fromNodeID : segment1.attributes.toNodeID) === 
			(segment2.reversed ? segment2.attributes.toNodeID : segment2.attributes.fromNodeID)) {
			return -1;
		}
		return 1;
	});
}

/**
 * Uses wLib to fetch route from routing server based on selected segments then
 * attempts to select the route segments.
 */
function performAutoSelect() {
	'use strict';
	var i,
		options,
		route,
		segmentsToSelect = [],
		selection = W.selectionManager.selectedItems,
		n = selection.length;

	/**
	 * Callback for the route selection utility. Gets the segment IDs of
	 * segments on the route, checks to see if they are loaded in WME and
	 * pushes them to array for selection later.
	 * @callback
	 */
	function routeCallback() {
		var segIDs, seg;
		segIDs = this.getRouteSegmentIDs()[0];
		segIDs.forEach(function (item) {
			seg = W.model.segments.get(item);
			if (seg) {
				segmentsToSelect.push(seg);
			}
		});
		if (this.last) {
			W.selectionManager.select(segmentsToSelect);
		}
	}

	/**
	 * Fetches the route (or sub-route) via wLib.
	 * @param {OpenLayers.Feature.Vector} start The starting segment of the sub-route.
	 * @param {OpenLayers.Feature.Vector} end The ending segment of the sub-route.
	 * @param {Boolean} last Whether the sub-route is the last of the total route.
	 * @param {Number} The timeout before fetching the route in milliseconds.
	 */
    function fetchRoute(start, end, last, timeout) {
        window.setTimeout(function () {
            route = new wLib.Model.RouteSelection(start, end, routeCallback, options);
            route.last = last ? true : false;
        }, timeout);
    }

	if (n > 0) {
		options = {
			fastest: $('#altFastest').prop('checked'),
			tolls: $('#altAvoidTolls').prop('checked'),
			freeways: $('#altAvoidFreeways').prop('checked'),
			dirt: $('#altAvoidDirt').prop('checked'),
			longtrails: $('#altAvoidLongDirt').prop('checked'),
			uturns: $('#altAllowUturns').prop('checked')
		};
		for (i = 0; i < n - 1; i++) {
			if ('segment' === selection[i].model.type && 'segment' === selection[i + 1].model.type) {
				fetchRoute(selection[i], selection[i + 1], i === n - 2 ? true : false, 500 * i);
			}
		}
	}
}

/**
 * Saves checkbox states to localStorage.
 */
function saveOptions(event) {
	'use strict';
	var options = options = {
		fastest: $('#altFastest').prop('checked'),
		tolls: $('#altAvoidTolls').prop('checked'),
		freeways: $('#altAvoidFreeways').prop('checked'),
		dirt: $('#altAvoidDirt').prop('checked'),
		longtrails: $('#altAvoidLongDirt').prop('checked'),
		uturns: $('#altAllowUturns').prop('checked'),
		highlights: $('#altHighlights').prop('checked'),
		sortByNode: $('#altSortByNode').prop('checked')
	};
	return window.localStorage.altNamesOptions = JSON.stringify(options);
}

/**
 * Loads checkbox states from localStorage.
 */
function loadOptions() {
	'use strict';
	var options;
	if (undefined !== typeof window.localStorage.altNamesOptions) {
		options = JSON.parse(window.localStorage.altNamesOptions);
		if (undefined !== typeof options.fastest) {
			$('#altFastest').prop('checked', options.fastest);
		}
		if (undefined !== typeof options.tolls) {
			$('#altAvoidTolls').prop('checked', options.tolls);
		}
		if (undefined !== typeof options.freeways) {
			$('#altAvoidFreeways').prop('checked', options.freeways);
		}
		if (undefined !== typeof options.dirt) {
			$('#altAvoidDirt').prop('checked', options.dirt);
		}
		if (undefined !== typeof options.longrails) {
			$('#altAvoidDirt').prop('checked', options.longtrails);
		}
		if (undefined !== typeof options.uturns) {
			$('#altAllowUturns').prop('checked', options.uturns);
		}
		if (undefined !== typeof options.highlights) {
			$('#altHighlights').prop('checked', options.highlights);
		}
		if (undefined !== typeof options.sortByNode) {
			$('#altSortByNode').prop('checked', options.sortByNode);
		}
	}
}

/**
 * Shows alert box with version information and changes.
 */
function updateAlert() {
	var altVersion = "0.40",
		alertOnUpdate = true,
		versionChanges = 'WME Show Alt Names has been updated to ' + altVersion + '.\n';
	versionChanges += 'Changes:\n';
	versionChanges += '[*]New feature: Highlighting for all segments with altnerate names.\n';
	versionChanges += '[*]New feature: Table sorting by "driving order."\n';
	versionChanges += '[*]New feature: User-selected options are saved across browser sessions.\n';
	if (alertOnUpdate && window.localStorage && window.localStorage.altVersion !== altVersion) {
		window.localStorage.altVersion = altVersion;
		alert(versionChanges);
	}
}

/**
 * Initializes the script by adding CSS and HTML to page, registering event listeners,
 * adding map layers, checking for beta editor, and running main functions to check for
 * segments loaded at init and highlighting.
 */
function init() {
	var altStyleMap, css, $header, highlightStyle, optionsHTML, $row;

	css = '#altTable {width: 100%;}';
    css += '.altTable, .altTable th, .altTable td {border: 1px solid white; padding: 3px; border-collapse: collapse; -moz-user-select: -moz-none; -khtml-user-select: none; -webkit-user-select: none;}\n';
    css += '.altTable-id {text-align: center; border-left: none;}\n';
    css += '.altTable-roadType {border-radius: 10px; color: black; text-shadow: 1px 1px 0 #fff,-1px -1px 0 #fff,1px -1px 0 #fff,-1px 1px 0 #fff,0px 1px 0 #fff,1px 0px 0 #fff,0px -1px 0 #fff,-1px 0px 0 #fff; border: 1px solid white; font-size: 0.8em; text-align: center; padding: 0 3px 0 3px; min-width: 32px;}';
    css += 'tr.altTable-selected > .altTable-id {font-weight: bold; background-color: white; color: black;}\n';

	css += '#altDiv {display: none; position: absolute; left: 6px; bottom: 60px; height: auto; width: auto; ';
	css += 'overflow-y: scroll; overflow-x: hidden; white-space: nowrap; background-color: rgba(0,0,0,0.8); color: white; padding: 5px; ';
	css += 'z-index: 1001; border-radius: 5px; max-height: 50%;}\n';

    // scroll bar CSS
    css += '#altDiv::-webkit-scrollbar {width: 15px; border-radius: 5px;}\n';
    css += '#altDiv::-webkit-scrollbar-track {border-radius: 5px; background: none; width: 10px;}\n';
    css += '#altDiv::-webkit-scrollbar-thumb {background-color: white; border-radius: 5px; border: 2px solid black;}\n';
	css += '#altDiv::-webkit-scrollbar-corner {background: none;}\n';

	// buttons css
	css += '.altOptions-button {margin: 0 0 3px 3px; height: 2em; font-size: 0.8em;}\n';
	
	// Options Menu CSS
	css += '#optionsDiv {display: none; clear: both; border: 1px solid white; padding: 3px; margin: 0 0 3px 0; font-weight: normal;}';
	css += '#optionsDiv td {padding-right: 5px;}';
	css += '#optionsDiv input {margin-right: 3px;}';
	
	//add css to page
	$('<style/>').html(css).appendTo($(document.head));

	// Make the options menu.
	optionsHTML = '<div id="altOptions"> <button id="altAutoSelect" class="altOptions-button" style="display: none;">Auto Select</button> <label style="float: right; margin: 3px;"><input id="altHighlights" type="checkbox">Highlight Alt Names</label> <button id="altOptionsButton" class="altOptions-button" style="float: right;">Show Options</button> </div> <div id="optionsDiv"> <label style="font-weight: normal;"><input type="checkbox" id="altSortByNode">Sort table by driving order</label> <form> <table> <thead> <tr> <td colspan="2" style="text-align: center; text-decoration: underline; font-weight: bold;">Auto Selection Route Options</td> </tr> </thead> <tbody> <tr> <td> <input type="checkbox" id="altAvoidTolls">Avoid toll roads</td> <td> <input type="checkbox" id="altAvoidFreeways">Avoid freeways</td> </tr> <tr> <td> <input type="checkbox" id="altAvoidLongDirt">Avoid long dirt roads</td> <td> <input type="checkbox" id="altAvoidDirt">Avoid dirt roads</td> </tr> <tr> <td> <input type="checkbox" id="altAllowUturns">Allow U-turns</td> <td> <input type="checkbox" id="altFastest">Fastest route</td> </tr> </tbody> </table> </form> </div>';
	
	// Make the table to hold segment information.
	$altTable = $('<table/>').attr('id', 'altTable').addClass('altTable');
	$header = $('<thead/>');
	$row = $('<tr/>').attr('id', 'altTable-header');
	$row.append($('<th/>').attr('colspan', '2').text('Segment ID'));
	$row.append($('<th/>').text('Primary'));
	$header.append($row);
	$altTable.append($header);
	$altTable.append('<tbody/>');
	
	// Make the main div to hold script content.
	$altDiv = $('<div/>').attr('id', 'altDiv');
	$altDiv.append(optionsHTML);
	$altDiv.append($altTable);
	$altDiv.appendTo($('#WazeMap'));
	$('#altAutoSelect').click(performAutoSelect);
	$('#altOptionsButton').click(function () {
		var $optionsDiv = $('#optionsDiv');
		if ($optionsDiv.css('display') === 'none') {
			$optionsDiv.show();
			$(this).text('Hide Options');
		} else {
			$optionsDiv.hide();
			$(this).text('Show Options');
		}
	});
	$('#altSortByNode').on('change', checkSelection);
	$('#altHighlights').on('change', checkAllSegments);
	$('#optionsDiv input[type=checkbox], #altOptions input[type=checkbox]').on('change', saveOptions);
    $altDiv.on('mouseenter mouseleave', 'td.altTable-primary, td.altTable-alt', { singleSegment: false }, applyHighlighting);
    $altDiv.on('dblclick', 'td.altTable-primary, td.altTable-alt', null, changeHighlightColor);
	$altDiv.on('mouseenter mouseleave', 'td.altTable-id', { singleSegment: true }, applyHighlighting);
	$altDiv.on('click', 'td.altTable-id', null, function () {
		panToSegment($(this).text());
	});
	$altDiv.on('dblclick', 'td.altTable-id', null, function () {
		selectSegment($(this).text());
	});
	
	// Create the map layers for segment highlighting.
    altStyleMap = new OL.StyleMap({
        default: new OL.Style({
            stroke: false
        }),
        highlight: new OL.Style({
			stroke: true,
            strokeWidth: 20,
            strokeColor: '${bgColor}',
            strokeOpacity: 1,
            strokeLinecap: 'round'
        })
    });
	altLayer = new OL.Layer.Vector('WME Show Alt Names', { styleMap: altStyleMap });
    altLayer.events.register('visibilitychanged', null, checkSelection);

    highlightStyle = new OL.StyleMap({
        default: new OL.Style({
            strokeWidth: 20,
            strokeColor: '#3399FF',
            strokeOpacity: 0.6
        })
    });
    highlightLayer = new OL.Layer.Vector('WME Show Alt Names - Highlight All', {
        styleMap: highlightStyle,
        displayInLayerSwitcher: false
    });

    W.map.addLayers([highlightLayer, altLayer]);
	
	//check for beta editor due to road layer name differences
	if (location.href.match(/-beta/)) {
		betaEditor = true;
	}
	
	//register WME event listeners
	W.loginManager.events.register('afterloginchanged', null, init);
	W.selectionManager.events.register('selectionchanged', null, checkSelection);
    W.map.events.register('moveend', null, checkAllSegments);
	W.map.getLayersByName('Roads')[0].events.register('visibilitychanged', null, checkSelection);
	if (betaEditor) {
		W.map.getLayersByName('Roads (old)')[0].events.register('visibilitychanged', null, checkSelection);
	} else {
		W.map.getLayersByName('Roads experimental')[0].events.register('visibilitychanged', null, checkSelection);
	}
	
	// Ready to go. Alert user to any updates and check for selected segments.
	updateAlert();
	loadOptions();
	checkAllSegments();
	checkSelection();
}

/**
 * Checks for key components of the page before initializing the script.
 */
function bootstrap() {
	var bGreasemonkeyServiceDefined = false;
	try {
		if ("object" === typeof Components.interfaces.gmIGreasemonkeyService) {
			bGreasemonkeyServiceDefined = true;
		}
	} catch (err) {
		/* Ignore. */
	}
	if (undefined === typeof unsafeWindow || !bGreasemonkeyServiceDefined) {
		unsafeWindow = (function () {
			var dummyElem = document.createElement('p');
			dummyElem.setAttribute('onclick', 'return window;');
			return dummyElem.onclick();
		})();
	}
	/* begin running the code! */
	if (undefined !== typeof $ &&
		$('#WazeMap').length !== 0 &&
		undefined !== typeof W.selectionManager.events.register &&
		undefined !== typeof W.loginManager.events.register) {
		window.setTimeout(init, 100);
	} else {
		window.setTimeout(function () {
			bootstrap();
		}, 1000);
	}
}

window.setTimeout(bootstrap, 100);