WME Show Alt Names

Shows alt names for selected segments

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            WME Show Alt Names
// @description     Shows alt names for selected segments
// @version         1.0.2
// @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
// @require         http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @require         http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js
// ==/UserScript==
/* global wLib */
/* global W */
/* global OL */

var jq214 = jQuery.noConflict(true);

(function ($) {
    var $altDiv,
        $altTable,
        alternateObjectsArray = [],
        altLayer,
        betaEditor,
        draggableSupported = true,
        highlightLayer,
        nameArray = [],
        selectedSegments = [],
        CSS = {
            'altTable': {
                'width': '100%'
            },
            'altTableClass': {
                'border': '1px solid white',
                'padding': '3px',
                'border-collapse': 'collapse',
                '-moz-user-select': '-moz-none',
                '-khtml-user-select': 'none',
                '-webkit-user-select': 'none'
            },
            'altTableType': {
                'width': '50px'
            },
            'altTableID': {
                'text-align': 'center',
                'border-left': 'none',
                'width': '80px'
            },
            'altTableRoadType': {
                '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'
            },
            'altTableSelected': {
                'font-weight': 'bold',
                'background-color': 'white',
                'color': 'black'
            },
            'altDiv': {
                'display': 'none',
                'position': 'absolute',
                'left': '6px',
                'bottom': '60px',
                'min-height': '120px',
                'min-width': '335px',
                'overflow-y': 'scroll',
                'overflow-x': 'hidden',
                'white-space': 'nowrap',
                'background-color': 'rgba(0,0,0,0.8)',
                'color': 'white',
                'padding': '5px'
            },
            'altDivScrollbar': {
                'width': '15px',
                'border-radius': '5px'
            },
            'altDivScrollbarTrack': {
                'border-radius': '5px',
                'background': 'none',
                'width': '10px'
            },
            'altDivScrollbarThumb': {
                'background-color': 'white',
                'border-radius': '5px',
                'border': '2px solid black'
            },
            'altDivScrollbarCorner': {
                'background': 'none'
            },
            'altOptionsButton': {
                'margin': '0 0 3px 3px',
                'height': '2em',
                'font-size': '0.8em'
            },
            'autoSelectButton': {
                'display': 'none'
            },
            'optionsDiv': {
                'display': 'none',
                'clear': 'both',
                'border': '1px solid white',
                'padding': '3px',
                'margin': ' 0 0 3px 0',
                'font-weight': 'normal'
            },
            'optionsDivTd': {
                'padding-right': '5px'
            },
            'optionsDivInput': {
                'margin-right': '3px'
            }
        },
        ROAD_TYPES = {
            1: { name: 'St', expColor: '#FFFFDD' },
            2: { name: 'PS', expColor: '#FDFAA7' },
            3: { name: 'Fwy', expColor: '#6870C3' },
            4: { name: 'Rmp', expColor: '#B3BFB3' },
            5: { name: 'Trl', expColor: '#B0A790' },
            6: { name: 'MH', expColor: '#469FBB' },
            7: { name: 'mH', expColor: '#69BF88' },
            8: { name: 'Dirt', expColor: '#867342' },
            10: { name: 'Bdwk', expColor: '#9A9A9A' },
            16: { name: 'Stwy', expColor: '#9A9A9A' },
            17: { name: 'Pvt', expColor: '#BEBA6C' },
            18: { name: 'RR', expColor: '#B2B6B4' },
            19: { name: 'Rwy', expColor: '#222222' },
            20: { name: 'PLR', expColor: '#ABABAB' }
            //add ferry
        };

	/**
	 * 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 intent 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 typeColor is the hex value of the display color.
	 */
    function getRoadColor(type) {
        if (type && undefined !== typeof ROAD_TYPES[type]) {
            return {
                typeString: ROAD_TYPES[type].name,
                typeColor: ROAD_TYPES[type].expColor
            };
        } else {
            return { typeString: 'error', typeColor: ROAD_TYPES[1].expColor };
        }
    }

	/**
	 * 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' : 'New road';
        this.primaryCity = city ? city.name || 'No city' : 'New road';

        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' : 'New road',
                city: city ? city.name || 'No city' : 'New road'
            });
        }

        // 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/>')
                .addClass('altTable-type')
                .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);
                if (event.data.singleSegment) {
                    colorSegment($this1.text());
                    $this1.parent().addClass('altTable-selected');
                } else {
                    colorFeatures($this1, $this1.css('background-color'));
                    if ($this1.hasClass('altTable-primary')) {
                        name1 = $this1.find('.altTable-primary-name').text();
                        city1 = $this1.find('.altTable-primary-city').text();
                    } else {
                        name1 = $this1.find('.altTable-alt-name').text();
                        city1 = $this1.find('.altTable-alt-city').text();
                    }
                    $('#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 > 100) {
                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(useFromNode) {
        'use strict';
        var path = [],
            startingNodeID = useFromNode ? selectedSegments[0].
                attributes.fromNodeID : selectedSegments[0].attributes.toNodeID;
        var findNextSegment = function (nodeID) {
            var fromNodeMatched = false,
                nextNode,
                tempPath = [],
                toNodeMatched = false;
            _.each(selectedSegments, function (segment) {
                console.debug('Checking segment ' + segment.attributes.id);
                if (path.indexOf(segment.attributes.id) !== -1) {
                    console.debug('Segment already in path.');
                    return;
                }
                if (segment.attributes.fromNodeID === nodeID) {
                    fromNodeMatched = true;
                    tempPath.push(segment);
                } else if (segment.attributes.toNodeID === nodeID) {
                    toNodeMatched = true;
                    tempPath.push(segment);
                }
            });
            if (tempPath.length === 1) {
                path.push(tempPath[0].attributes.id);
                if (fromNodeMatched) {
                    nextNode = tempPath[0].attributes.toNodeID;
                } else if (toNodeMatched) {
                    nextNode = tempPath[0].attributes.fromNodeID;
                }
                findNextSegment(nextNode);
            }
        };

        path.push(selectedSegments[0].attributes.id);

        console.debug('Looking for connected segments at node ' +
            startingNodeID);
        findNextSegment(startingNodeID);

        if (path.length === 0 && !useFromNode) {
            console.debug('No connections at toNode. Looking at fromNode.');
            sortSegmentsByNode(true);
        } else {
            selectedSegments = _.sortBy(selectedSegments, function (segment) {
                return path.indexOf(segment.attributes.id);
            });
        }
    }


	/**
	 * 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 = {
            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 = '1.0.2',
            alertOnUpdate = true,
            versionChanges = 'WME Show Alt Names has been updated to ' + altVersion + '.\n';
        versionChanges += 'Changes:\n';
        versionChanges += '[*] Table can be moved and resized.\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-type {width: 50px;}\n';
        css += '.altTable-id {text-align: center; border-left: none; width: 80px}\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; min-height: 120px; min-width: 335px;';
        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));
        
        // add jqui style to page
        $('head').append($('<link/>').attr({
            href: '//ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/le-frog/jquery-ui.min.css',
            rel: 'stylesheet',
            type: 'text/css'
        }));

        // 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
        try {
            $altDiv.one('resize', function () {
                $(this).css('max-height', '100%');
            });
            $altDiv.resizable({ handles: 'all', containment: 'parent' });
            $altDiv.one('drag', function () {
                $(this).css('bottom', 'auto');
            });

            $altDiv.draggable({ containment: 'parent' });
        } catch (err) {
            draggableSupported = false;
        };
	
        // 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: '#FFFFFF',
                strokeOpacity: 0.4
            })
        });
        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);
	
        // 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').size() &&
            window.W.selectionManager.events.register &&
            window.W.loginManager.events.register) {
            init();
        } else {
            setTimeout(function () {
                bootstrap();
            }, 1000);
        }
    }
    debugger;
    bootstrap();
} (jq214));