WME Show Alt Names

Shows alt names for selected segments

目前為 2015-09-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name            WME Show Alt Names
// @description     Shows alt names for selected segments
// @version         0.50
// @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==
/* global wLib */
/* global W */
/* global $ */
/* global jQuery */
/* global OL */

(function () {
	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),
			name = $this.find('.altTable-primary-name').text() || $this.find('.altTable-alt-name').text(),
			city = $this.find('.altTable-primary-city').text() || $this.find('.altTable-alt-city').text(),
			useCity = $('#altUseCity').prop('checked');
		for (i = 0, n = nameArray.length; i < n; i++) {
			if (nameArray[i].name === name && (useCity ? nameArray[i].city === city : true)) {
				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
		},
			roadsExpLayerVisible = W.map.getLayersByName('Roads')[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,
			useCity = $('#altUseCity').prop('checked');

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

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

	/**
	 * Populates the table with segment information
	 * @param {Number} maxAlternates The maxiumum number of alternates
	 * a segment has (how many "Alt" columns are needed).
	 * @param {Boolean} sortByNode Whether to sort the table in driving
	 * order by node ID.
	 */
	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));
		}
		
		$('#altShowCity').change();
		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 {jQuery} $el The cell from the table to match.
	 * @param {String} color The rgba-formatted color value.
	 */
	function colorFeatures($el, color) {
		'use strict';
		var i,
			n,
			j,
			t,
			colorValues,
			feature,
			names,
			name = $el.find('.altTable-primary-name').text() || $el.find('.altTable-alt-name').text(),
			city = $el.find('.altTable-primary-city').text() || $el.find('.altTable-alt-city').text(),
			useCity = $('#altUseCity').prop('checked');
    
		//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++) {
				if (names[j].name === name && (useCity ? names[j].city === city : true)) {
					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, name1, city1,
			useCity = $('#altUseCity').prop('checked');
		switch (event.type) {
			case 'mouseenter':
				$this1 = $(this);
				name1 = $this1.find('.altTable-primary-name').text() || $this1.find('.altTable-alt-name').text();
				city1 = $this1.find('.altTable-primary-city').text() || $this1.find('.altTable-alt-city').text();
				if (event.data.singleSegment) {
					colorSegment($this1.text());
				} else {
					colorFeatures($this1, $this1.css('background-color'));
				}
				$('#altTable tbody td').each(function (index) {
					var $this2 = $(this),
						name2 = $this2.find('.altTable-primary-name').text() || $this2.find('.altTable-alt-name').text(),
						city2 = $this2.find('.altTable-primary-city').text() || $this2.find('.altTable-alt-city').text();
					if (name1 === name2 && (useCity ? city1 === city2 : true)) {
						$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.selectionCountByType.segment && 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, 1000 * 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'),
			useCity: $('#altUseCity').prop('checked'),
			showCity: $('#altShowCity').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);
			}
			if (undefined !== typeof options.useCity) {
				$('#altUseCity').prop('checked', options.useCity);
			}
			if (undefined !== typeof options.useCity) {
				$('#altShowCity').prop('checked', options.showCity);
			}
		}
	}

	/**
	 * Shows alert box with version information and changes.
	 */
	function updateAlert() {
		var altVersion = "0.50",
			alertOnUpdate = true,
			versionChanges = 'WME Show Alt Names has been updated to ' + altVersion + '.\n';
		versionChanges += 'Changes:\n';
		versionChanges += '[*] Added options for controlling display and matching of city names.\n';
		versionChanges += '[*] Increased time between route requests to prevent server errors.\n';
		versionChanges += '[*] Increased the maximum height of the table window.';
		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: 60%;}\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"> <div> <label style="font-weight: normal;"> <input type="checkbox" id="altShowCity">Show city name in table</label> </div> <div> <label style="font-weight: normal;"> <input type="checkbox" id="altUseCity">Use city name in name matching</label> </div> <div> <label style="font-weight: normal;"> <input type="checkbox" id="altSortByNode">Sort table by driving order (experimental)</label> </div> <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);
		$('#altUseCity').on('change', colorTable);
		$('#altShowCity').on('change', function () {
			if ($(this).prop('checked')) {
				$altTable.find('.altTable-primary-city, .altTable-alt-city').each(function () {
					$(this).show();
				});
			} else {
				$altTable.find('.altTable-primary-city, .altTable-alt-city').each(function () {
					$(this).hide();
				});
			}
		});
		$('#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());
		});
	
		/* Make $altDiv resizable and draggable
		$altDiv.one('resize', function () {
			$(this).css('max-height', '100%');
		});
		$altDiv.resizable({ handles: 'all', containment: 'parent' });
		$altDiv.draggable({ containment: 'parent' });
		*/
	
		// 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 (old)')[0].events.register('visibilitychanged', null, checkSelection);
		W.map.getLayersByName('Roads')[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() {
		if (undefined !== typeof $ &&
			undefined !== typeof wLib &&
			$('#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);
} ());