WME Road Selector Highlights

Adds the ability to highlight segments that meet a set of custom conditions. Requires WME Road Selector to function.

当前为 2016-03-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            WME Road Selector Highlights
// @namespace       https://greasyfork.org/users/11629-TheLastTaterTot
// @version         0.6.1
// @description     Adds the ability to highlight segments that meet a set of custom conditions. Requires WME Road Selector to function.
// @author          TheLastTaterTot
// @include         https://editor-beta.waze.com/*editor/*
// @include         https://www.waze.com/*editor/*
// @exclude         https://www.waze.com/*user/editor/*
// @require         https://greasyfork.org/scripts/17641-rsel-exprparser-basic/code/rsel-exprparser-basic.js?version=111918
// @run-at          document-end
// ==/UserScript==
//---------------------------
// DEBUG:
//RSelEP = RSelExprParser;
//---------------------------
// TODO: Add option to draw highlights as under segments rather than on top (z=334) -- put it under show add'l line opts
//       Add option to turn off RSel Highlights (not just hide the layer)

var RSel, rsh, rsh_seg, rsh_OL,
    isFirefox = /\bfirefox\b/i.test(navigator.userAgent);

function RSH() {
    this._clone = function (obj) {
        if (!obj) {
            return JSON.parse(JSON.stringify(this));
        } else {
            return JSON.parse(JSON.stringify(obj));
        }
    };

    this._seq = function (b) {
        return Array.apply(null, Array(b)).map(function (_, c) {
            return c;
        });
    };

    this._sum = function (arr) {
        return arr.reduce(function (a, b) {
            return a + b
        });
    };

    this.presets = {
        linePropObjKeys: [
            'strokeColor',
            'strokeOpacity',
            'strokeDashstyle',
            'strokeLinecap',
            'strokeWidthScale',
            'strokeDashSizeScale',
            'strokeGapScale'
        ],
        linePropIds: ['selRSHColors', 'numRSHOpacity', 'selRSHDash',
            'selRSHCap', 'numRSHScaleWidth', 'numRSHDashSizeScale',
            'numRSHGapScale'
        ],
        linePropAttrType: ['value', 'valueAsNumber', 'selectedIndex',
            'selectedIndex', 'valueAsNumber', 'valueAsNumber', 'valueAsNumber'
        ],
        lineAttributes: {
            strokeWidth: [4, 5, 7, 8, 9, 11, 13, 14, 14, 15, 15],
            strokeDashstyle: ["solid", "- - - - -", "– ‧ – ‧"],
            strokeDasharray: [
                ["solid"],
                [1, 1],
                [2, 1, 1, 1]
            ],
            strokeLinecap: ["butt", "round", "square"]
        },
        defaultLineStyle: {
            strokeColor: "#FFFF00",
            strokeOpacity: 0.5,
            strokeDashstyle: 0,
            strokeLinecap: 0,
            strokeWidthScale: 1,
            strokeDashSizeScale: 1,
            strokeGapScale: 1
        }
    };

    this.STYLES = {Default: this._clone(this.presets.defaultLineStyle)};

    this.getNewRuleObject = function(){
        return {expr: {}, text: null, isValidated: null};
    };

    this.getNewStylesObject = function() {
        return this._clone(this.STYLES.Default);
    };

    this.HIGHLIGHTS = [{
        rule: this.getNewRuleObject(),
        style: this.getNewStylesObject()
    }];

    this.lastIdx = 0;
    this.idx = 0;
    this.rulesStack = [0];
    this.prevZoom = null;
    this.doNotDrawLastHighlight = true;
    this.triggerUpdate = true;
    this.showSessionHighlights = false;
    this.loadedSavedSession = false;

    this.storeExpression = function(idx){
        if (!idx) idx = this.idx;
        var currExpr = RSel.getCurrentExpression();
        this.HIGHLIGHTS[idx].rule = {
            expr: (currExpr) ? currExpr : {},
            text: (currExpr) ? RSel.getExpressionText(currExpr) : null,
            isValidated: null
        };
    };

    this.clearStoredExpression = function(idx) {
        if (!idx) idx = this.idx;
        this.HIGHLIGHTS[idx].rule = this.getNewRuleObject()
    };

    this.storeExprText = function(idx) {
        if (!idx) idx = this.idx;
        this.HIGHLIGHTS[idx].rule.text = RSel.getExpressionText(RSel.getCurrentExpression());
        this.HIGHLIGHTS[idx].rule.isValidated = null;
    };

    this.validateExprText = function(idx, clearExprBox) {
        if (!idx) idx = this.idx;
        document.getElementById('outRSExpr').style.display = 'none';

        if (rsh.HIGHLIGHTS[idx].rule.text) {
            //console.log(rsh.HIGHLIGHTS[idx].rule.text);
            RSelExprParser.updateExpression(rsh.HIGHLIGHTS[idx].rule.text);
            var currExprText = document.getElementById('outRSExpr').value;

            if (currExprText === rsh.HIGHLIGHTS[idx].rule.text) {
                this.storeExpression(idx); // update the expr obj in case it needs to be
                this.HIGHLIGHTS[idx].rule.isValidated = true;
                //$('#outRSExpr').wrap('<div id="divRSExpr" style="padding: 4px; border: 1px solid transparent;"></div>');
                //document.getElementById('outRSExpr').style.padding = '2px 0';
                //document.getElementById('outRSExpr').style.border = '1px solid transparent';
                //document.getElementById('outRSExpr').style.borderColor = '#8000FF';
            } else {
                this.HIGHLIGHTS[idx].rule.isValidated = false
                if (!clearExprBox) {
                    document.getElementById('outRSExpr').innerHTML = '\
                    <div style="padding: 4px; background: repeating-linear-gradient(45deg, rgba(251,255,0,0.2), rgba(251,255,0,.2) 10px, rgba(255,224,0,0.4) 10px, rgba(255,224,0,0.4) 20px)">' +
                    document.getElementById('outRSExpr').innerHTML + '</div>' +
                    '<div style="font-size: 10px; color: crimson; font-weight: 600; outline: darkgray dotted 1px; margin-top: 5px; padding: 3px 5px;">' +
                    '<i class="fa fa-exclamation-triangle"></i> <b>RSel Highlights</b> was unable to parse the saved expression text to exactly match the original expression.' +
                    '<div style="font-size: 10px; font-weight: 600; margin-top: 4px;">' +
                    'Your original selection will be used for highlighting, but it cannot be edited – only deleted. ' +
                    '<b>Please make a bug report <a href="https://www.waze.com/forum/posting.php?mode=reply&f=819&t=173107" target="_blank">here</a></b>.' +
                    '</div></div>';
                    document.getElementById('outRSExpr').style.display = 'block';
                }
            }
        }
        if (clearExprBox) RSelExprParser.rselButtons.clear();

        document.getElementById('outRSExpr').style.display = 'block';

        return this.HIGHLIGHTS[idx].rule.isValidated;
    };

    this.recreateExpressionText = function(idx) {
        if (!idx) idx = rsh.idx;

        if (this.validateExprText(idx)) {
            return this.HIGHLIGHTS[idx].rule.text;
        } else if (rsh.HIGHLIGHTS[idx].rule.expr) { //resort to using
            return RSel.getExpressionText(rsh.HIGHLIGHTS[idx].rule.expr)
        }
    };

    this.addHighlighter = function () {
        RSelExprParser.rselButtons.clear();

        this.idx = this.HIGHLIGHTS.length; //next index
        this.HIGHLIGHTS[this.idx] = {
            rule: this.getNewRuleObject(),
            style: this.getNewStylesObject()
        };
        this.lastIdx = this.idx;
        this.rulesStack[this.idx] = this.idx;
        this.triggerUpdate = true;
        this.doNotDrawLastHighlight = true;
    };

    this.deleteHighlighter = function () {
        RSelExprParser.rselButtons.clear();

        this.HIGHLIGHTS.splice(this.idx, 1);
        ((this.idx - 1) > 0) ? this.idx--: this.idx = 0;
        this.lastIdx = this.HIGHLIGHTS.length - 1;
        this.rulesStack = this._seq(this.HIGHLIGHTS.length);
        this.triggerUpdate = true;
    };

    /*this._getNonemptyExprIndexes = function (arrOfExpr) {
        try {
            var reverseIndices = [],
                e = arrOfExpr.length,
                r = 0;

            while (e--) {
                if (arrOfExpr[e].expr !== undefined && arrOfExpr[e].expr != null &&
                    Object.keys(arrOfExpr[e].expr).length !== 0)
                    reverseIndices[r++] = e;
            }
            return reverseIndices;
        } catch (err) {
            console.error(err);
        }
    }
    // this._removeEmpty(arr,{ii}) - ii is an optional array of indices of arr to keep
    this._removeEmpty = function (arr, ii) { //----Note: the expected indices should be reverse order (last->first)
        //bc of the optimized negative while loop
        try {
            var reverseIndices = [],
                arrnew = [],
                e = arr.length,
                r = 0;
            if (typeof ii === 'undefined') {
                while (e--) {
                    if (arr[e] !== undefined && arr[e] != null &&
                        arr[e].length !== undefined && Object.keys(arr[e]).length !== 0)
                        reverseIndices[r++] = e;
                }
            } else {
                reverseIndices = ii;
                r = ii.length;
            }

            while (r--) {
                arrnew[r] = arr[reverseIndices[r]];
            }
            return arrnew;
        } catch (err) {
            console.error(err);
        }
    }
    this.housekeeping = function () {
        var numLayers = this.HIGHLIGHTS.length,
            notEmptyIndexes = this._getNonemptyExprIndexes(this.HIGHLIGHTS),
            numNewLayers = notEmptyIndexes.length,
            p = this.presets.linePropObjKeys.length,
            lineProp;

        this.HIGHLIGHTS = this._removeEmpty(this.HIGHLIGHTS, notEmptyIndexes);
        this.rulesStack = this._removeEmpty(this.rulesStack, notEmptyIndexes);

        if (numLayers !== numNewLayers) {
            this.lastIdx = numNewLayers - 1;
            this.idx = this.rulesStack.indexOf(this.idx);
            if (this.idx === -1) this.idx = this.lastIdx;
            return true;
        } else {
            return false;
        }
    };*/

    this.importHighlights = function (addThis) {
        this.HIGHLIGHTS = addThis.HIGHLIGHTS;
        this.rulesStack = addThis.rulesStack;
        //this.housekeeping(); // -- TODO: annoying... keeps flipping the order. fix this, then reenable.
        this.lastIdx = this.HIGHLIGHTS.length - 1;
        this.idx = this.lastIdx;
        this.triggerUpdate = true;
        this.loadedSavedSession = true;
        this.showSessionHighlights = !!addThis.showSessionHighlights;
        this.doNotDrawLastHighlight = !!addThis.doNotDrawLastHighlight;
    };

    this.getCurrentExprText = RSelExprParser.getCurrentExprText;
}

//------------------------------------------------------------------------------

/*////////////////////////////////////////////////////////////////////////////*/
//
//  TODO: Organize code...convert some functions to methods and prototypes
//
function RSelHighlights() {
    function updatePrefsFromPanel() {
        rsh.HIGHLIGHTS[rsh.idx].style.strokeColor = document.getElementById("selRSHColors").value;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeOpacity = document.getElementById("numRSHOpacity").valueAsNumber;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeDashstyle = document.getElementById("selRSHDash").selectedIndex;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeLinecap = document.getElementById("selRSHCap").selectedIndex;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeWidthScale = document.getElementById("numRSHScaleWidth").valueAsNumber;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeDashSizeScale = document.getElementById("numRSHDashSizeScale").valueAsNumber;
        rsh.HIGHLIGHTS[rsh.idx].style.strokeGapScale = document.getElementById("numRSHGapScale").valueAsNumber;
        rsh.storeExpression();
    }

    var updateHighlighterColorButton = function () {
        // -----------------------------------------------------------------
        // Recolor background of Highlight button
        //$("#selRSHColors").val(hex).change();
        document.getElementById('selRSHColorBtn').style.backgroundColor =
            rsh.HIGHLIGHTS[rsh.idx].style.strokeColor;
        document.getElementById('selRSHColors').value =
            rsh.HIGHLIGHTS[rsh.idx].style.strokeColor;


    }


    var updateDashButtons = function () {
        // Check to enable or disable line option input boxes
        var dashSizeScaleObj = document.getElementById("numRSHDashSizeScale"),
            strokeGapScaleObj = document.getElementById("numRSHGapScale");

        if (rsh.HIGHLIGHTS[rsh.idx].style.strokeDashstyle !== 0) {
            dashSizeScaleObj.disabled = false;
            strokeGapScaleObj.disabled = false;
            dashSizeScaleObj.style.backgroundColor = "#FFF";
            strokeGapScaleObj.style.backgroundColor = "#FFF";
        } else {
            dashSizeScaleObj.disabled = true;
            strokeGapScaleObj.disabled = true;
            dashSizeScaleObj.style.backgroundColor = "#E0E0E0";
            strokeGapScaleObj.style.backgroundColor = "#E0E0E0";
        }
    }

    function updateEraseButton(disableBtn) {
        if (disableBtn === undefined) {
            try {
                if (!rsh.showSessionHighlights || rsh.doNotDrawLastHighlight) {
                    disableBtn = true;
                } else if (rsh.lastIdx === 0 &&
                    rsh.HIGHLIGHTS[rsh.idx].rule.expr &&
                    Object.keys(rsh.HIGHLIGHTS[rsh.idx].rule.expr).length !== 0) {
                    disableBtn = false;
                } else {
                    disableBtn = true;
                }
            } catch (err) {
                disableBtn = true;
            }
        }
        if (disableBtn) {
            $("a#btnRSHClearTop").addClass('disabled');
        } else {
            $("a#btnRSHClearTop").removeClass('disabled');
        }
    }

    function updateLayerCountButtons() {
        if (rsh.lastIdx !== 0) {
            $("a#btnRSHSubtract").removeClass('disabled');
            $("a#btnRSHClearAll").removeClass('disabled')
            updateEraseButton(false);
        } else {
            $("a#btnRSHSubtract").addClass('disabled');
            $("a#btnRSHClearAll").addClass('disabled');
            updateEraseButton();
        }

        $("#txtRSHCount").html(rsh.idx + 1);
    }

    function updatePanelFromPrefs() {
        //console.log('updatePanelFromPrefs()')

        rsh.storeExpression(); //update rsh obj with current expression
        // Apply panel settings from saved preferences
        //$("#selRSHColors").val(rsh.HIGHLIGHTS[rsh.idx].style.strokeColor).change();
        $("#numRSHOpacity").val(rsh.HIGHLIGHTS[rsh.idx].style.strokeOpacity).change();
        $("#selRSHDash").val(rsh.presets.lineAttributes.strokeDashstyle[rsh.HIGHLIGHTS[rsh.idx].style.strokeDashstyle]).change();
        $("#selRSHCap").val(rsh.presets.lineAttributes.strokeLinecap[rsh.HIGHLIGHTS[rsh.idx].style.strokeLinecap]).change();
        $("#numRSHScaleWidth").val(rsh.HIGHLIGHTS[rsh.idx].style.strokeWidthScale).change();
        $("#numRSHDashSizeScale").val(rsh.HIGHLIGHTS[rsh.idx].style.strokeDashSizeScale).change();
        $("#numRSHGapScale").val(rsh.HIGHLIGHTS[rsh.idx].style.strokeGapScale).change();

        updateHighlighterColorButton();
        updateDashButtons();
        updateLayerCountButtons();
    }

    // ---------------------------------------------------------------------
    var setupLineStyles = function () {
        var bgRoadColor = document.getElementById("selRSHBgRoadColor").value,
            numHighlights = rsh.rulesStack.length;

        updatePrefsFromPanel();

        // preset line attributes for each zoom level
        var z = 11;
        while (z--) { // for each zoom level
            rsh_seg[z] = {
                hlLineStyle: [],
                hlLineStyleOvrlp: [],
                bgLineStyle: {}
            };

            var strokeWidthAtZoom = rsh.presets.lineAttributes.strokeWidth[z],
                bgStrokeWidthAtZoom = strokeWidthAtZoom - parseInt(strokeWidthAtZoom *
                    0.5),
                mm = numHighlights;

            while (mm--) {
                // Stroke width (line thickness)
                var separationAmt = parseInt((strokeWidthAtZoom) / numHighlights),
                    strokeWidthScale = rsh.HIGHLIGHTS[mm].style.strokeWidthScale,
                    strokeWidthAtZoomScaledOvrlp = Math.round(((strokeWidthAtZoom + 2) - mm *
                        separationAmt) * strokeWidthScale),
                    strokeWidthAtZoomScaled = Math.round(strokeWidthAtZoom *
                        strokeWidthScale),
                    strokeOpacity = rsh.HIGHLIGHTS[mm].style.strokeOpacity,
                    strokeOpacityOvrlp = strokeOpacity;

                // Dash style and customized gap size
                var dashSpecs = rsh.presets.lineAttributes.strokeDasharray[
                        rsh.HIGHLIGHTS[mm].style.strokeDashstyle],
                    dashSizeScale = rsh.HIGHLIGHTS[mm].style.strokeDashSizeScale,
                    gapScale = rsh.HIGHLIGHTS[mm].style.strokeGapScale,
                    dashSize = dashSizeScale * strokeWidthAtZoomScaled,
                    gap = gapScale * strokeWidthAtZoomScaled,
                    dashSizeOvrlp = dashSizeScale * strokeWidthAtZoomScaledOvrlp,
                    gapOvrlp = gapScale * strokeWidthAtZoomScaledOvrlp,
                    strokeDashstyle, strokeDashstyleOvrlp;

                // Make width 1 if the strokeWidth is less than 1 at this zoom
                // Also increase opacity to compensate for the small strokeWidth
                if (strokeWidthAtZoomScaled <= 1) {
                    strokeWidthAtZoomScaled = 1;
                    if (strokeOpacity < 0.9) strokeOpacity = 0.9;
                }

                // adjust strokeWidth for multiple highlights
                strokeOpacityOvrlp = rsh.HIGHLIGHTS[mm].style.strokeOpacity;
                if (strokeWidthAtZoomScaledOvrlp <= 1) {
                    strokeWidthAtZoomScaledOvrlp = 1;
                    if (strokeOpacityOvrlp < 0.9) strokeOpacityOvrlp = 0.9;
                }

                switch (rsh.HIGHLIGHTS[mm].style.strokeDashstyle) {
                    case 0:
                        strokeDashstyle = dashSpecs[0];
                        strokeDashstyleOvrlp = dashSpecs[0];
                        break;
                    case 1:
                        strokeDashstyle = dashSpecs[0]*dashSize + ' ' + dashSpecs[1]*gap;
                        strokeDashstyleOvrlp = dashSpecs[0]*dashSizeOvrlp + ' ' + dashSpecs[1]*gapOvrlp;
                        break;
                    case 2:
                        strokeDashstyle = dashSpecs[0]*dashSize + ' ' + dashSpecs[1]*gap +
                            ' ' + dashSpecs[2]*strokeWidthAtZoomScaled + ' ' + dashSpecs[3]*gap;
                        strokeDashstyleOvrlp = dashSpecs[0]*dashSizeOvrlp + ' ' + dashSpecs[1]*gapOvrlp +
                            ' ' + dashSpecs[2]*strokeWidthAtZoomScaledOvrlp + ' ' + dashSpecs[3]*gapOvrlp;
                        break;
                }

                rsh_seg[z].hlLineStyle[mm] = {
                    strokeColor: rsh.HIGHLIGHTS[mm].style.strokeColor,
                    strokeOpacity: strokeOpacity,
                    strokeDashstyle: strokeDashstyle,
                    strokeLinecap: rsh.presets.lineAttributes.strokeLinecap[rsh.HIGHLIGHTS[mm].style.strokeLinecap],
                    strokeWidth: strokeWidthAtZoomScaled,
                    graphicZIndex: mm + 1
                };

                rsh_seg[z].hlLineStyleOvrlp[mm] = {
                    strokeColor: rsh_seg[z].hlLineStyle[mm].strokeColor,
                    strokeOpacity: strokeOpacityOvrlp,
                    strokeDashstyle: strokeDashstyleOvrlp,
                    strokeLinecap: rsh_seg[z].hlLineStyle[mm].strokeLinecap,
                    strokeWidth: strokeWidthAtZoomScaledOvrlp,
                    graphicZIndex: mm + 1
                };
            }

            // lineStyle for background (Bg) highlighting
            if (bgStrokeWidthAtZoom < 1) bgStrokeWidthAtZoom = 1;

            rsh_seg[z].bgLineStyle = {
                strokeColor: bgRoadColor,
                strokeOpacity: 0.9,
                strokeWidth: bgStrokeWidthAtZoom,
                strokeLinecap: "butt",
                strokeDashstyle: "solid",
                graphicZIndex: 0
            };
        }

        rsh.triggerUpdate = false;
    }
    // ---------------------------------------------------------------------

    function updateExprMenu() {
        //console.log('updateExprMenu()')
        // Dropup menu for selecting layers
        var e = rsh.HIGHLIGHTS.length, htmlText = '',
            exprText, selStatus;

        while (e-- > 0) {
            if (e === rsh.idx) {
                selStatus = 'active ';
                exprText = rsh.recreateExpressionText(e);
            } else {
                selStatus = '';
                exprText = RSel.getExpressionText(rsh.HIGHLIGHTS[e].rule.expr);
            }
            if (!exprText) exprText = '';

            htmlText += (e !== 0) ? '<li>' : '<li style="border-bottom: 0">';

            htmlText += '<a id="liRSHexpr" name="' + e + '" ' +
                'class = "rsh-expr-menu ' + selStatus +
                '" ' + 'href="javascript:void(0)">' +
                '<div class="rsh-btn-row">' +
                '<div class="icon-sign-blank fa fa-square rsh-expr-color" style="color:' +
                rsh.HIGHLIGHTS[e].style.strokeColor + '"></div>' +
                exprText + ' </div> ' +
                '</a></li>';
        }

        $(".rsh-expr-menu").html(htmlText);

        updatePanelFromPrefs();

        //------
        $("a.rsh-expr-menu").click(function () {
            if (rsh.triggerUpdate) Highlight();
            rsh.idx = parseInt(this.name);
            //console.log(rsh.idx);
            updateExprMenu();
        });
    }

    // ---------------------------------------------------------------------
    function drawHighlights() {
        var currExpr = RSel.getCurrentExpression();
        if (currExpr !== null) {
            rsh.storeExpression();
            //rsh.HIGHLIGHTS[rsh.idx].rule.expr = currExpr;
        }
        rsh.doNotDrawLastHighlight = false;
        updateExprMenu();
        rsh.showSessionHighlights = true;
        rsh_OL.setVisibility(true);
        updateEraseButton(false);

        Highlight();
        sessionStorage.RSHighlights = JSON.stringify(rsh);
    }

    function eraseHighlight() {
        //rsh.HIGHLIGHTS[rsh.idx].rule = {};
        rsh.doNotDrawLastHighlight = true;
        updateEraseButton(true);
        rsh.clearStoredExpression();
        updateExprMenu();
        Highlight();
    }

    function clearAllHighlights() {
        RSelExprParser.rselButtons.clear();
        rsh = new RSH();
        rsh.doNotDrawLastHighlight = true;
        rsh_OL.destroyAllFeatureMaps();
        rsh_OL.setVisibility(false);

        updateExprMenu(); //reset expressions menu -- also updates panel with settings from rsh.HIGHLIGHTS[].style
        setupLineStyles(); //reset line attributes in rsh.HIGHLIGHTS[].style

        sessionStorage.RSHighlights = null;
    }

    // ---------------------------------------------------------------------
    function addNewHighlighter() {
        drawHighlights();

        var currExpr = RSel.getCurrentExpression();
        if ((rsh.HIGHLIGHTS[rsh.idx].rule.text === null || rsh.HIGHLIGHTS[rsh.idx].rule.text.length === 0) && currExpr === null) {
            return false;
        } else {
            rsh.addHighlighter();
            rsh.doNotDrawLastHighlight = true;
            //updatePanelFromPrefs();
            updateExprMenu();// -- also updates panel with settings from rsh.HIGHLIGHTS[].style

            sessionStorage.RSHighlights = JSON.stringify(rsh);
            return true;
        }
    }

    function deleteHighlighter() {
        if (rsh.lastIdx > 0) {
            rsh.deleteHighlighter();
            //updatePanelFromPrefs();
            updateExprMenu();// -- also updates panel with settings from rsh.HIGHLIGHTS[].style

            sessionStorage.RSHighlights = JSON.stringify(rsh);
            return true;
        } else {
            return false;
        }
    }

    // -----------------------------------------------------------------------------
    var toggleDropBarMenu = function (hide) {
        var dropbarPanel = document.getElementById("divRSHdropbarPanel"),
            dropbarHeader = document.getElementById("divRSHdropbarHeader"),
            dropbarContents = document.getElementById("divRSHdropbarContents"),
            mainPanel = document.getElementById("divRSHmainPanel");

        if (hide) {
            mainPanel.style.borderBottomLeftRadius = "5px";
            mainPanel.style.borderBottomRightRadius = "5px";
            dropbarPanel.style.borderWidth = 0;
            dropbarPanel.style.backgroundColor = "transparent";
            dropbarHeader.style.backgroundColor = "transparent";
            dropbarHeader.style.bottomBorderWidth = 0;
            document.getElementById('aRSHdropbarLinkShow').style.display = 'inline-block';
            document.getElementById('aRSHdropbarLinkHide').style.display = 'none';
            $('div.rsh-styles-menu').css('display','none');
            dropbarContents.style.display = "none";
        } else {
            mainPanel.style.borderBottomLeftRadius = 0;
            mainPanel.style.borderBottomRightRadius = 0;
            dropbarPanel.style.borderWidth = "2px";
            dropbarPanel.style.backgroundColor = "#F9F9F9";
            dropbarHeader.style.backgroundColor = "#DADBDC";
            dropbarHeader.style.bottomBorderWidth = "1px";
            document.getElementById('aRSHdropbarLinkShow').style.display = 'none';
            document.getElementById('aRSHdropbarLinkHide').style.display = 'inline-block';
            $('div.rsh-styles-menu').css('display','inline-block');
            dropbarContents.style.display = "block";
        }
    };

    // -----------------------------------------------------------------------------
    // -----------------------------------------------------------------------------
    function Highlight(optionalArg) {
        var checkAllSegs = false;
        if (rsh_OL.visibility) {
            if (rsh.triggerUpdate) {
                setupLineStyles();
                rsh_OL.destroyFeaturesByMapKey(rsh.idx);
            }

            if (optionalArg === null) checkAllSegs = true;

            var userBgHighlight = document.getElementById("cbRSHBgHighlight").checked,
                userEditable = document.getElementById("cbRSEditable").checked,
                userSuppressRoad = document.getElementById("cbRSHSuppRoad").checked,
                currentZoom = Waze.map.zoom,
                hlLineStyle = rsh._clone(rsh_seg[currentZoom].hlLineStyle),
                hlLineStyleOvrlp = rsh_seg[currentZoom].hlLineStyleOvrlp,
                bgLineStyle = rsh_seg[currentZoom].bgLineStyle,
                segments = Waze.model.segments.objects, //***********
                highlighterIndexes, newFeatureArray = [], newBgFeatureArray = [],
                delFeatureArray = [], delBgFeat;

            if (rsh.doNotDrawLastHighlight) {
                if (userSuppressRoad) optionalArg = true; //must redraw because drawing of features with this setting enabled might change depending on which highlight rules are active

                highlighterIndexes = rsh.rulesStack.filter(function(a){return a!==rsh.idx});
                rsh_OL.destroyFeaturesByMapKey(rsh.idx);
                /*var destroyThese = rsh_OL.getFeaturesByAttribute('rshIndex',rsh.idx);
                if (destroyThese.length) {
                    rsh_OL.unsetFeatureMappings(destroyThese);
                    rsh_OL.destroyFeatures(destroyThese);
                }*/
            } else {
                highlighterIndexes = rsh.rulesStack;
            }

            if (optionalArg === true) {
                //console.log('Reset requested: rsh_OL.destroyAllFeatureMaps();')
                rsh_OL.destroyAllFeatureMaps();
            }

            //Define separate objects to store features for each highlight rule
            rsh.rulesStack.map(function(h){if(rsh_OL._featureMap[String(h)] === undefined) rsh_OL._featureMap[String(h)] = {};});

            var ids = Object.keys(segments),
                s = ids.length, seg, countHighlighted, h, H;

            while (s--) {
                if (checkAllSegs || rsh_OL.isOnScreen(ids[s])) { //only care about onscreen segments
                    seg = segments[ids[s]]; //segments.get(ids[s]),

                    countHighlighted = 0;
                    for (h of highlighterIndexes) {
                        H = String(h);
                        if ((seg.arePropertiesEditable() || !userEditable) &&
                            RSel.checkSegment(rsh.HIGHLIGHTS[h].rule.expr, seg)) { //test against selection criteria
                            countHighlighted++;

                            if (!rsh_OL._featureMap[H][ids[s]]) {//if highlight has not already been drawn...
                                if (countHighlighted === 1) {
                                    newFeatureArray.push(rsh_OL.createLineFeature(seg, hlLineStyle[h], h, countHighlighted * userSuppressRoad));
                                } else {
                                    newFeatureArray.push(rsh_OL.createLineFeature(seg, hlLineStyleOvrlp[h], h));
                                }
                            }  //else if (optionalArg && optionalArg.constructor !== Boolean) {// else redraw??
                                //console.log(optionalArg);
                            //}
                        } else if (rsh_OL._featureMap[H][ids[s]]) { //seg no longer meets selection criteria, but it is still drawn...
                            delFeatureArray.concat(rsh_OL.getFeaturesByAttribute('rshID', 'h'+h+'-'+ids[s])); //log it for removal
                        }
                    }
                    //--------------------------------------------------------------------------------------
                    // Check if script should highlight "background roads" (i.e., roads that don't meet any of the other highlight criteria)
                    if (userBgHighlight && countHighlighted === 0 && !rsh_OL._featureMap.bg[ids[s]]) {
                        newBgFeatureArray.push(rsh_OL.createLineFeature(seg, bgLineStyle, 'bg'));
                    } else if (userBgHighlight && rsh_OL._featureMap.bg[ids[s]] && countHighlighted !== 0){
                        requestAnimationFrame(function(){
                            rsh_OL.destroyFeaturesBy('id',ids[s]);
                            /*delBgFeat = rsh_OL.getFeaturesByAttribute('id',ids[s]);
                            if (delBgFeat.length) {
                                rsh_OL.unsetFeatureMappings(delBgFeat);
                                rsh_OL.destroyFeatures(delBgFeat);
                            }*/
                        });
                    } //------------------------------------------------------------------------------------
                } //isOnScreen
            } //while

            if (newFeatureArray.length) rsh_OL.addFeatures(newFeatureArray);
            if (newBgFeatureArray.length) rsh_OL.addFeatures(newBgFeatureArray);

            if (!userBgHighlight) {
                requestAnimationFrame(function(){rsh_OL.destroyFeaturesByMapKey('bg')});
                /*delBgFeat = rsh_OL.getFeaturesByAttribute('rshIndex','bg');
                if (delBgFeat.length) {
                    rsh_OL._featureMap.bg = {};
                    rsh_OL.destroyFeatures(delBgFeat);
                }*/
            }

            requestAnimationFrame(function(){rsh_OL.destroyFeatureArray(delFeatureArray)});

            // Check if time to perform cleanup
            for (h of rsh.rulesStack) {
                H = String(h);
                if (rsh_OL._featureMap[H] && Object.keys(rsh_OL._featureMap[H]).length > 5000) rsh_OL.cleanupFeatureMap(H);
            }
            if (Object.keys(rsh_OL._featureMap.bg).length > 10000) rsh_OL.cleanupFeatureMap('bg');

        } else {
            rsh.showSessionHighlights = false;
        }
    }

    // -----------------------------------------------------------------------------
    var initHighlightsLayer = function () {
        var rsh_OL = new OL.Layer.Vector("Road Selector Highlights", { //W.map.getLayersByName("Road Selector Highlights")[0]
            rendererOptions: { zIndexing: true },
            uniqueName: '__RSel_Highlights',
            displayInLayerSwitcher: true,
            draggable:true
        });

        I18n.translations.en.layers.name.__RSel_Highlights = 'Road Selector Highlights';
        Waze.map.addLayer(rsh_OL);
        Waze.map.addControl(new OL.Control.DrawFeature(rsh_OL, OL.Handler.Path));
        rsh_OL.setZIndex(501);
        rsh_OL.setVisibility(rsh.showSessionHighlights);
        rsh_OL._featureMap = {bg: {}};

        /*rsh_OL.createBgLineFeature = function (segID, nodes, backgroundLine) {
            return new OL.Feature.Vector(new OL.Geometry.LineString(nodes), null, backgroundLine);
        };*/
        rsh_OL._clone = rsh._clone;

        rsh_OL.createLineFeature = function (seg, lineOptions, rshIndex, stack) {
            var segID = seg.attributes.id,
                vertices = seg.geometry.components,
                rshID = 'h'+rshIndex+'-'+segID;

            if (stack === undefined) { /* do nothing */
            } else if (stack === 1) {
                lineOptions.strokeOpacity = 0.9;
            }

            this._featureMap[String(rshIndex)][segID] = seg.geometry.bounds;
            return new OL.Feature.Vector(new OL.Geometry.LineString(vertices), {id: segID, rshIndex: rshIndex, rshID: rshID, stack: stack}, lineOptions);
        };

        rsh_OL.unsetFeatureMappings = function (featArray) {
            var numRemoved = 0;
            featArray.map(function(a,i){
                numRemoved++;
                rsh_OL._featureMap[String(a.attributes.rshIndex)][a.attributes.id] = undefined;
            });
            //console.info('WMERSH:','Feature(s) unmapped =',numRemoved);
        };

        rsh_OL.destroyFeatureArray = function (featArray) {
            if (featArray.length) {
                this.destroyFeatures(featArray);
                this.unsetFeatureMappings(featArray);
            }
        };

        rsh_OL.destroyFeaturesBy = function(attrName, attrVal) {
            var destroyThese = this.getFeaturesByAttribute(attrName,attrVal);
            this.destroyFeatureArray(destroyThese);
            /*if (destroyThese.length) {
                this.unsetFeatureMappings(destroyThese);
                this.destroyFeatures(destroyThese);
            }*/
        };

        rsh_OL.destroyFeaturesByMapKey = function(featMapKey) {
            if (featMapKey === undefined) featMapKey = rsh.idx;
            var destroyThese = this.getFeaturesByAttribute('rshIndex',featMapKey);
            if (destroyThese.length) {
                this._featureMap[String(featMapKey)] = {};
                //rsh_OL.unsetFeatureMappings(destroyThese);
                this.destroyFeatures(destroyThese);
            }
        };

        rsh_OL.destroyAllFeatureMaps = function() {
            this.destroyFeatures();
            this._featureMap = {bg: {}};
        };

        rsh_OL.cleanupFeatureMap = function(featMapKey) {
            if (featMapKey === undefined) featMapKey = String(rsh.idx);
            console.log('rsh_OL.cleanupFeatureMap("' + featMapKey + '")');
            requestAnimationFrame(function() {
                //calculate current active bounds at same scale
                var currentCenter = W.map.getCenter(), //lon-lat
                    scalingFactor = W.map.getResolutionForZoom(W.map.zoom), //OL.Util.getScaleFromResolution(W.map.getResolutionForZoom(zoomOfDataExtent), W.map.baseLayer.units),
                    newDataBounds = W.map.getExtent().scale(scalingFactor, currentCenter),
                    featKeysSegID = Object.keys(rsh_OL._featureMap[featMapKey]),
                    nfeatKeysSegID = featKeysSegID.length,
                    f, segID, segBounds, featureMapBits = {};

                for (f = featKeysSegID; f--;) {
                    segID = featKeysSegID[f];
                    segBounds = rsh_OL._featureMap[featMapKey][segID];

                    if ( segBounds !== undefined &&
                        newDataBounds.intersectsBounds(segBounds)) {
                        featureMapBits[segID] = segBounds;
                    }
                }

                console.log(Object.keys(featureMapBits).length);
                rsh_OL._featureMap[featMapKey] = featureMapBits;
            });
        };

        rsh_OL.isOnScreen = function(segID){
            return W.map.getExtent().intersectsBounds(W.model.segments.objects[segID].geometry.bounds);
        };

        return rsh_OL;
    };
    // -----------------------------------------------------------------------------
    var closeExprMenu = function(){
        document.getElementById('btnRSHmenu').classList.remove('open');
        window.removeEventListener('click',closeExprMenu,false);
    };

    var styleMenuDelete = function(that) {
        delete rsh.STYLES[that.parentNode.name];
        that.parentNode.parentNode.remove();
        localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
    };

    var stylesMenuActions = function(e) {
        switch (this.name) {
            case 'saveas':
                e.stopPropagation();
                var styleName = prompt("Please enter a name for your style:");
                rsh.STYLES[styleName] = rsh.HIGHLIGHTS[rsh.idx].style;
                $('.rsh-styles-menu ul').append('<li><a name="' + styleName + '">' + styleName + '<span class="fa fa-trash fa-pull-right fa-lg"></span></a></li>');
                $('.rsh-styles-menu li>a').click(stylesMenuActions);
                $('.rsh-styles-menu li>a>span.fa-trash').click(function(e){
                    e.stopPropagation();
                    styleMenuDelete(this);
                });
                localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
                break;
            case 'setasdefault':
                rsh.STYLES.default = rsh._clone(rsh.HIGHLIGHTS[rsh.idx].style);
                rsh.STYLES.default.strokeColor = '#FFFF00';
                localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
                break;
            case 'resetdefault':
                rsh.STYLES.default = rsh._clone(rsh.presets.defaultLineStyle);
                localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
                break;
            default:
                var strokeColor = rsh.HIGHLIGHTS[rsh.idx].style.strokeColor;
                rsh.HIGHLIGHTS[rsh.idx].style = rsh._clone(rsh.STYLES[this.name]);
                rsh.HIGHLIGHTS[rsh.idx].style.strokeColor = strokeColor;
                updatePanelFromPrefs();
                rsh.triggerUpdate = true;
                requestAnimationFrame(Highlight);
        }
    };
    var copyToClipboard = function(str) {
        var $temp = $("<input>");
        $("body").append($temp);
        $temp.val(str).select();
        document.execCommand("copy");
        $temp.remove();
    };

    var pasteFromClipboard = function() {
        var $temp = $("<input>"),
        str;

        $("body").append($temp);
        $temp.focus();
        $temp.val('.').select();
        document.execCommand("paste");
        str = $temp.val()
        $temp.remove();
        console.log(str);
        return str;
    }

    // -----------------------------------------------------------------------------
    var InitRSelHighlights = function () { // return rsh

        var checkForAutosave = function () { // return rsh
            rsh = new RSH();

            // Load autosaved parameters from sessionStorage
            if (sessionStorage.RSHighlights) {
                rsh.importHighlights(JSON.parse(sessionStorage.RSHighlights));
                //console.log("WMERSH: Imported data from previous session.")

                if (rsh.HIGHLIGHTS.length === 0 ||
                    (rsh.HIGHLIGHTS.length !== 0 && rsh.HIGHLIGHTS[0].rule.text === undefined)) rsh = new RSH();
            }
            //if (rsh.loadedSavedSession) {if (rsh.HIGHLIGHTS[rsh.lastIdx].rule !== null) rsh.addHighlighter(); }
            if (rsh.showSessionHighlights === undefined) rsh.showSessionHighlights = false;


            if (localStorage.RSHighlights === undefined) localStorage.RSHighlights = '{}';

            if (localStorage.RSHighlights_Styles === undefined) {
                localStorage.RSHighlights_Styles = '';
            } else {
                try {
                    var rshStyles = JSON.parse(localStorage.RSHighlights_Styles);
                    if (rshStyles.constructor === Object) rsh.STYLES = rshStyles;
                } catch(err) { /* ignore */ }
            }

            if (localStorage.RSHighlights_Prefs === undefined) {
                localStorage.RSHighlights_Prefs = '';
            } else {
                document.getElementById('cbRSHSuppRoad').checked = /&s/.test(localStorage.RSHighlights_Prefs); //suppress
                document.getElementById("cbRSHBgHighlight").checked = /&t/.test(localStorage.RSHighlights_Prefs); //trace
            }

            return rsh;
        };


        var setupRSHInterface = function () {
            // CSS for RSel Highlights
            var rshCSS = document.createElement("style");
            rshCSS.type = "text/css";
            // UI panel
            rshCSS.innerHTML =
                '.rsh-main-panel { min-width: 285px; width: 100%; background-color: #BEDCE5; border-style: solid; border-width: 2px; border-collapse: collapse; ' +
                '   padding: 5px 8px; border-color: #93C4D3; margin-top: 15px; border-radius: 5px; vertical-align: middle; }\n' +
                '.rsh-dropbar-panel { color: #444; border-style: inset; margin-top: 0; border-width: 2px; border-color: #D5D5D5; background-color: #f9f9f9; ' +
                '   border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }\n' + // border-bottom-left-radius: 5px; border-bottom-right-radius: 5px0
                '.rsh-dropbar-panel table { border: 0; width: 100%; display: inline-table;}\n' +
                '.rsh-dropbar-panel td { font-size: 7pt; font-weight: 600; line-height: 6px; padding: 0px 4px 4px 4px; margin: 0; vertical-align: middle; }\n' +
                '.rsh-arrow-up { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 5px solid black; }\n' +
                '.rsh-arrow-down { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid black; }\n' +
                'div.rsh-vcentered { position: relative; padding: 2px 5px 3px; width: 100%; display: inline-block; vertical-align: middle; text-align: left; }\n' +
                '.rsh-dropbar-contents {display: block; margin: 8px 0px 0px; padding: 0; border: 0 }\n' +
                'select.rsh-dropbar-panel { height:20px; width: 100px; background-color: #FFF; border: 1px solid #959595; margin: 0px 0px 2px 2px; padding: 0; }\n' +
                'input.rsh-dropbar-panel { height:20px; width: 36px; background-color: #FFF; border: 1px solid #C1C1C1; border-radius: 0; margin: 0px 0px 2px 2px; padding: 0; }\n' +
                '.rsh-dropbar-header { color: #000; border-bottom: 1px solid #c0c0c0; }\n' +
                'a.rsh-link { text-decoration: none; }' +
                '.rsh-btn-container>label { padding-left: 4px; }';

            // Buttons
            rshCSS.innerHTML +=
                '.rsh-btn-row { position: relative; display: block; vertical-align: middle; margin: auto; padding: 0; }\n' +
                '.rsh-btn-container { height: 25px; position: relative; display: inline-block; padding: 0; margin: 0px 1%; vertical-align: middle; }\n' +
                '.rsh-btn-drop { height: 22px; background-color: #FEFEFE; border: 1px solid #CBE1E8; margin-top: 0px; margin-bottom: 0px !important}\n' +
                '.rsh-btn-color { border-radius: 5px; line-height: 2; width: 24px; height: 25px; position: absolute; top: 0px; left: 0px; margin-right: -3px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; padding: 0; border-bottom: 2px solid rgba(0,0,0,0.3);}\n' +
                '.rsh-btn-highlight { border-radius: 4px; width: 75px; padding-right: 5px; border-top-left-radius: 0px; border-bottom-left-radius: 0px; }\n' + //border-bottom: solid 2px #E4E4E4; border-top: 0; border-right: 0; border-left: 0; }' +
                '.rsh-btn-counter, .rsh-btn-counter:hover, .rsh-btn-counter:active, .rsh-btn-counter.active, .rsh-btn-counter:focus { z-index: 0 !important;' +
                '   height: 25px; width: 25px; padding: 3px 4px; font-size: 14px; line-height: 1.4; cursor: pointer; color: #88888E; background-color: #CFE0E6; border-left: solid 1px #7AB0BE; border-bottom: 0; ' +
                '   box-shadow: inset 0px 2px 4px -1px rgba(0,0,0,0.2), inset 0px -2px 4px -2px rgba(0,0,0,0.2) }\n' +
                '.rsh-btn-inner { border-radius: 5px; background-color: white; display: inline-block; }\n' +
                '.rsh-btn { border-radius: 4px; font-size: 12px; line-height: 1.5; height: 25px; font-weight: bold; color: #396179; padding: 3px;}\n' + //background-color: #93C1D3;
                '.rsh-btn:hover { opacity: 0.95; color: #264C5F; }\n' + //color: #48758C;
                '.rsh-btn.disabled { color: #6C9AAE; cursor: not-allowed !important; opacity: .85;}\n' + //box-shadow: inset 0px 0px 0px 1px rgba(0,0,0,0.05);
                '.rsh-btn:active, .rsh-btn:focus, .rsh-btn.active { background-image: none; outline: 0; -webkit-box-shadow: none; box-shadow: none; }\n' +
                'a#btnRSHClearAll.rsh-btn { padding-top: 2px; border-left: solid 1px #B099B1; color: white; width: 25px;}\n' +
                'a#btnRSHClearAll.rsh-btn.disabled { background-color: #ff8383; color: #FFE0E0 !important; opacity: 0.75;}\n' +
                '.rsh-checkbox-row { width: 100%; padding: 0px 8px; display: inline;}\n' +
                '.rsh-checkbox-row label { color: #396179; font-size: 10.5px; font-weight: 600 !important;}\n' +
                '.rsh-btn>.tooltip-inner { font-weight: bold; }\n' +
                '.fa-bars.rsh-icn { margin-left: 0; margin-right: -1%; line-height: 0.9; padding: 4px; height: 25px; display: inline-block; vertical-align: middle; color: #59899E; font-size: 17px; text-shadow: 0px 1px 0px rgba(255,255,255,0.5); border: 1px solid transparent; }\n' +
                '.rsh-icn:hover, .rsh-icn:active, .rsh-icn:focus { border: 1px solid rgb(132, 174, 191); border-radius: 4px; cursor: pointer; }\n' +
                '.rsh-styles-menu>ul>li>a { padding: 2px 15px; cursor: pointer; }\n' +
                '.rsh-styles-menu .fa-trash:hover, .rsh-styles-menu .fa-trash:focus, .rsh-styles-menu .fa-trash:active {color: #ed503b;}\n' +
                '.rsh-styles-menu .fa-trash { margin-top: 2.5px; color: lightgray; }\n' +
                '.btn-group .rsh-btn2 { font-size: 13px; width: 21px; height: 22px; padding: 1px; border-width: 1px; border-radius: 3px; color: #396179; background: transparent; }\n' +
                '.btn-group .rsh-btn2:focus, .btn-group .rsh-btn2:active { border: 1px solid #00BCD4; z-index: 2;}\n';

            rshCSS.innerHTML += // Dropup expressions menu
                '.dropdown-menu.rsh-expr-menu { right: -90px; padding: 1px; border-radius: 2px; font-size: 10px; max-height: 400px; width: 290px; overflow-x: hidden; overflow-y: auto;}\n' +
                '.dropdown-menu.rsh-expr-menu>li { border-bottom: 1px solid #CBE1E8; margin: 0; padding: 0; }\n' +
                '.dropdown-menu.rsh-expr-menu>li>a:active, .dropdown-menu.rsh-expr-menu>li>a.active { background-color: #D8E7EC; }\n' +
                '.dropdown-menu.rsh-expr-menu>li>a { word-wrap: break-word; white-space: normal; padding-top: 8px; padding-bottom: 8px; padding-left:10px; padding-right:10px; }\n' +
                '.divider.rsh-expr-menu { background-color: #CBE1E8; color: #CBE1E8; margin: 0; padding: 0; height: 2px; }\n' +
                '.rsh-expr-color { display: inline-block; margin-left: 0px; margin-right: 8px; padding: 0; }\n' +
                '';

            // CSS-generated icons
            rshCSS.innerHTML +=
                '.rsh-icn-outerbox { display: inline-block; width: 13px; height: 11px; margin: 5px 3px 6px 3px; padding: 0; font-size: 0; border: 0; vertical-align: middle; line-height: 0px; }\n' +
                '.rsh-icn-indark { display: inline-block; margin: 0; padding: 0; background-color: #555; }\n' +
                '.rsh-icn-inwhite { display: inline-block; margin: 0; padding: 0; background-color: #FFF; }\n' +
                '.rsh-icn-inclear { display: inline-block; margin: 0; padding: 0; background-color: transparent; }\n' +
                '.rsh-icn-brdark { display: block; margin: 0; padding: 0; background-color: #555; }\n' +
                '.rsh-icn-brclear { display: block; margin: 0; padding: 0; background-color: transparent; }\n' +
                '.rsh-icn-ingray { display: inline-block; margin: 0; padding: 0; background-color: #bbbbbb; }\n' +
                '.rsh-icn-2x2 { width: 2px; height: 2px }\n' +
                '.rsh-icn-3x2 { width: 3px; height: 2px }\n' +
                '.rsh-icn-2x1 { width: 2px; height: 1px }\n' +
                '.rsh-icn-1x2 { width: 1px; height: 2px }\n' +
                '.rsh-icn-1x1 { width: 1px; height: 1px }\n' +
                '.rsh-icn-3x3 { width: 3px; height: 3px }\n' +
                '.rsh-icn-4x3 { width: 4px; height: 3px }\n' +
                '.rsh-icn-13x1 { width: 13px; height: 1px }\n';

            document.body.appendChild(rshCSS);

            //------------------------------------------------------------------
            // CSS-HTML Icons
            // 		[weight, opacity, stroke type, end cap, dash size, dash gap]
            var rshIcons = [
			'<div class="rsh-icn-outerbox"><div class="rsh-icn-brdark" style="width: 13px; height: 3px"></div><div class="rsh-icn-brclear" style="width: 13px; height: 2px"></div><div class="rsh-icn-brdark" style="width: 13px; height: 2px"></div><div class="rsh-icn-brclear" style="width: 13px; height: 3px"></div><div class="rsh-icn-brdark" style="width: 13px; height: 1px"></div></div>',
			'<div class="rsh-icn-outerbox"><div class="rsh-icn-indark rsh-icn-3x3"></div><div class="rsh-icn-inwhite rsh-icn-3x3"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #ababab !important"></div><div class="rsh-icn-inwhite rsh-icn-3x3" style="background-color: #f1f1f1 !important"></div><div class="rsh-icn-inwhite rsh-icn-3x3"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #999999 !important"></div><div class="rsh-icn-inwhite rsh-icn-3x3" style="background-color: #f1f1f1 !important"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #CCCCCC !important"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #888888 !important"></div><div class="rsh-icn-inwhite rsh-icn-3x3" style="background-color: #fafafa !important"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #bbbbbb !important"></div><div class="rsh-icn-inclear rsh-icn-3x3"></div><div class="rsh-icn-inwhite rsh-icn-3x3" style="background-color: #fbfbfB !important"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #bbbbbb !important"></div><div class="rsh-icn-inclear rsh-icn-3x3"></div><div class="rsh-icn-indark rsh-icn-3x3" style="background-color: #dddddd !important"></div></div>',
			'<div class="rsh-icn-outerbox"><div class="rsh-icn-brdark" style="width: 13px; height: 2px"></div><div class="rsh-icn-brclear" style="width: 13px; height: 2px"></div><div class="rsh-icn-indark" style="width: 6px; height: 2px"></div><div class="rsh-icn-inclear" style="width: 1px; height: 2px"></div><div class="rsh-icn-indark" style="width: 6px; height: 2px"></div><div class="rsh-icn-brclear" style="width: 13px; height: 2px"></div><div class="rsh-icn-indark" style="width: 3px; height: 3px"></div><div class="rsh-icn-inclear" style="width: 2px; height: 3px"></div><div class="rsh-icn-indark" style="width: 3px; height: 3px"></div><div class="rsh-icn-inclear" style="width: 2px; height: 3px"></div><div class="rsh-icn-indark" style="width: 3px; height: 3px"></div></div>',
			'',
			'<div class="rsh-icn-outerbox" style="width: 13px !important"><div class="rsh-icn-indark rsh-icn-1x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-1x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-1x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-1x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-1x2"></div><!-->>--><div class="rsh-icn-brclear" style="width: 13px; height: 3px"></div><!--<<--><div class="rsh-icn-indark rsh-icn-3x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-3x2"></div><div class="rsh-icn-inclear rsh-icn-2x2"></div><div class="rsh-icn-indark rsh-icn-3x2"></div><!-->>--><div class="rsh-icn-brclear" style="width: 13px; height: 2px"></div><!--<<--><div class="rsh-icn-indark" style="width: 6px; height: 2px"></div><div class="rsh-icn-inclear" style="width: 2px; height: 2px"></div><div class="rsh-icn-indark" style="width: 5px; height: 2px"></div></div>',
			'<div class="rsh-icn-outerbox"><div class="rsh-icn-brclear" style="width: 13px; height: 1px"></div><div class="rsh-icn-indark rsh-icn-4x3"></div><div class="rsh-icn-inclear" style="width: 5px; height: 3px"></div><div class="rsh-icn-indark rsh-icn-4x3"></div><div class="rsh-icn-brclear" style="width: 13px; height: 3px"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-ingray rsh-icn-1x1"></div><div class="rsh-icn-inclear" style="width: 7px; height: 1px"></div><div class="rsh-icn-ingray rsh-icn-1x1"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-ingray" style="width: 9px; height: 1px"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-ingray rsh-icn-1x1"></div><div class="rsh-icn-inclear" style="width: 7px; height: 1px"></div><div class="rsh-icn-ingray rsh-icn-1x1"></div><div class="rsh-icn-inclear rsh-icn-2x1"></div><div class="rsh-icn-brclear" style="width: 13px; height: 2px"></div></div>'
			];
            //------------------------------------------------------------------
            // Main RSel Highlights panel
            $("#RSselection").append(
                '<div class="rsh-main-panel" id="divRSHmainPanel" style="padding-bottom: 5px;">' +
                '<div class="rsh-btn-row">' +
                '	<div class="rsh-btn-container">' +
                '   <div class="rsh-btn-inner" style="margin-right: 6px;">' +
                '		<input type="color" id="selRSHColors" value="#FFFF00" class="btn rsh-btn-color" style="opacity: 0; float: left; position: relative; padding: 0; -webkit-appearance: none; -moz-appearance: none;" ' +
                '			   list="colors" data-toggle="tooltip" title="Highlighter color">' + // highlight color
                '       <datalist id="colors">' +
                '			<option>#CCFF66</option><option>#28F311</option><option>#66FFCC</option><option>#00F2F2</option><option>#0037D1</option>' +
                '			<option>#FFFF00</option><option>#F5950E</option><option>#E1201B</option><option>#FF00FF</option><option>#8000FF</option>' +
                '		</datalist></input>' +
                '       <div id="selRSHColorBtn" class="btn rsh-btn-color fa fa-caret-down fa-lg" style="pointer-events: none; z-index: 1; background-color: yellow;"></div>' +
                '		<button id="btnRSHighlight" class="btn btn-primary rsh-btn rsh-btn-highlight active" ' + // HIGHLIGHT
                '			data-toggle="tooltip" title="Update map highlights"> Highlight</button>' +
                '   </div><div class="rsh-btn-inner" style="margin-right: 6px;">' +
                '		<a id="btnRSHClearTop" style="width: 26px;" class="btn btn-primary rsh-btn disabled"  data-toggle="tooltip" title="Erase last highlight" href="javascript:void(0)">' + // undo eraser
                '		    <span class="fa fa-eraser fa-lg"></span></a>' +
                '	</div></div>' +
                '	<div class="rsh-btn-container"><div class="btn-group rsh-btn-inner">' +
                '		<a id="btnRSHAdd" style="width: 24px;" class="btn btn-primary rsh-btn" data-toggle="tooltip" title="Add new highlighter rule" href="javascript:void(0)">' + // Add layer
                '			<span class="fa fa-plus"></span></a>' +
                '		<div id="btnRSHmenu" class="btn-group dropup">' +
                '			<div id="btnRSHCount" class="btn rsh-btn-counter">' +// layer counter
                '			<span id="txtRSHCount">0</span></div>' + // [#]
                '			<ul class="dropdown-menu pull-right dropdown-menu-right list-group rsh-expr-menu">' +
                '				<li></li>' +
                '			</ul>' +
                '		</div>' +
                '		<a id="btnRSHSubtract" style="width: 24px;" class="btn btn-primary rsh-btn disabled" data-toggle="tooltip" title="Delete selected highlight rule" href="javascript:void(0)">' + // border-right: solid 1px #7AB0BE;
                '			<span class="fa fa-minus"></span></a>' +
                '		<a id="btnRSHClearAll" class="btn btn-danger rsh-btn disabled"  data-toggle="tooltip" title="Delete all highlighter rules" href="javascript:void(0)">' +
                '			<span class="fa fa-trash fa-lg"></span></a>' +
                '   </div></div>' +
                '   <div id="icnRSHmoreOpts" class="fa fa-bars pull-right rsh-icn" title="More highlight options" data-toggle="tooltip"></div>' +
                '</div></div>');

            $("#divRSHmainPanel").append(
                '<div id="rshMoreOptions" style="display: none;">' +
                '<div class="rsh-btn-row" style="margin: 6px -8px 0px; padding: 4px 0px 4px; border-top: 1px solid #93C4D3; border-bottom: 1px solid #93C4D3; background-color: #A1C4D1;">' +
                '	<div class="rsh-checkbox-row">' +
                '   <div class="checkbox-inline" style="width: width: 270px; display: inline-block; padding-left: 20px;">' +
                '		<input type="checkbox" id="cbRSHSuppRoad" class="btn" style="margin-right: 5px; margin-bottom: 7px;"></input>' +// Suppress roads
                '       <label for="cbRSHSuppRoad">Suppress apperance of roads under highlights</label>' +
                '   </div>' +
                '	</div>' +
                '	<div class="rsh-checkbox-row">' +
                '   <div class="checkbox-inline" style="width: 210px; display: inline-block; padding-left: 20px;">' +
                '       <input type="checkbox" id="cbRSHBgHighlight"  class="btn">' +
                '       <label for="cbRSHBgHighlight">Trace over non-highlighted roads with a single color</label>' + // Trace background roads checkbox
                '   </div>' +
                '   <div style="display: inline-block">' +
                '       <input type="color" id="selRSHBgRoadColor" value="#c0c0c0" class="btn rsh-btn-drop" ' + // trace color
                '           list="bgRdcolors" style="width: 50px; height: 21px; padding: 2px 4px; background-color: #CBE1E8; position: relative; right: 3px; top: -2px;" ' +
                '           data-toggle="tooltip" title="Tracing color for non-highlighted roads">' +
                '       <datalist id="bgRdcolors">' +
                '           <option>#FFFFFF</option><option>#c0c0c0</option><option>#000000</option><option>#264B01</option><option>#634D41</option>' +
                '       </datalist>' +
                '   </div>' +
                '	</div>' +
                '</div>' +
                '<div class="form-inline" style="vertical-align: middle; margin: 6px -2px 0px -2px;">' +
                  '<select id="selRSHighlighter" class="form-control" style="padding: 0; width: 145px; min-width: 145px; height: 24px;"><option></option></select>' +
                  '<div class="btn-group" style="float: right; margin: 1px;">' +
                    '<a id="applyRSHighlighter" title="Load selected highlighter" style="margin-left: 5px;" class="btn btn-primary rsh-btn2"><i class="fa fa-upload fa-fw"></i></a>' +
                    '<a id="saveRSHighlighter" title="Save current highlighting rules" class="btn btn-primary rsh-btn2"><i class="fa fa-save fa-fw"></i></a>' +
                    '<a id="delRSHighlighter" title="Delete selected highlighter" class="btn btn-primary rsh-btn2"><i class="fa fa-times fa-fw"></i></a>' +
                    '<a id="importRSHighlighter" title="Import highlighters from clipboard" class="btn btn-primary rsh-btn2"><i class="fa fa-sign-in fa-flip-horizontal fa-fw"></i></a>' +
                    '<a id="exportRSHighlighter" title="Export selected highlighter" class="btn btn-primary rsh-btn2"><i class="fa fa-reply fa-flip-horizontal fa-fw"></i></a>' +
                    '<a id="exportAllRSHighlighter" title="Export all highlighters" class="btn btn-primary rsh-btn2"><i class=" fa fa-reply-all fa-flip-horizontal fa-fw"></i></a>' +
                  '</div>' +
                '</div>' +
                '</div>');
            
            if (isFirefox) $('#cbRSHBgHighlight').parent().css('padding-left','28px');

            $("div#divRSHmainPanel [data-toggle=tooltip]").tooltip({
                placement: 'auto top',
                delay: {
                    show: 1100,
                    hide: 100
                },
                html: true,
                template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="my-tooltip-header"><b></b></div><div class="my-tooltip-body tooltip-inner" style="font-weight: bold; !important"></div></div>'
            });

            // Additional style options drop menu
            $("#RSselection").append('\
                <div id="divRSHdropbarPanel" class="rsh-dropbar-panel" style="min-width: 285px; width: 100%;">\
                <div id="divRSHdropbarHeader" class="rsh-vcentered">\
                <div style="display: inline-block">\
                  <a id="aRSHdropbarLinkShow" class="rsh-link" href="javascript:void(0);" style="display: none;"><i class="fa fa-chevron-up"></i>&nbsp;Show additional style options</a>\
                  <a id="aRSHdropbarLinkHide" style="display: inline-block;" class="rsh-link" href="javascript:void(0);"><i class="fa fa-chevron-down"></i>&nbsp;Hide additional style options</a>\
                </div>\
                <div class="dropdown fa-pull-right rsh-styles-menu open" style="display: none;">\
                    <button class="btn dropdown-toggle" type="button" data-toggle="dropdown" style="padding: 0; background: transparent; border: 0; color: #888; font-size: 11px; height: 100%;">\
                    <span class="fa fa-caret-square-o-down"></span></button>\
                    <ul class="dropdown-menu" style="top: 80%; left: initial; right: -3px; border-radius: 2px; font-size: 10px; min-width: 140px; max-width: 140px;">\
                      <li><a name="saveas" href="#">Save Style As...</a></li>\
                      <li><a name="saveasdefault" href="#">Save As Default</a></li>\
                      <li><a name="resetdefault" href="#">Reset Default Style</a></li>\
                      <li class="divider"></li>\
                      <li class="dropdown-header" style="padding: 2px 15px; font-size: 9px; text-transform: uppercase;">Load Saved Style</li>\
                      <li><a name="Default" href="#">Default</a></li>\
                    </ul>\
                </div>\
                </div>' + //closing for divRSHdropbarHeader
                '<div id="divRSHdropbarContents" class="rsh-dropbar-contents">' +
                // table for additional style options within dropbar panel
                '<table class="rsh-dropbar-panel">' +
                '<tr><td style="vertical-align: bottom">Thickness</td><td style="vertical-align: bottom">Opacity</td>' +
                '<td rowspan=4>' +
                // inner table for dash style options
                '<table class="rsh-dropbar-panel" style="padding-left: 3px; padding-right: 3px; border-left: 1px solid #DFDFDF !important">' +
                '<tr><td colspan=2 style="vertical-align: bottom">Dash style</td>' +
                '</tr><tr><td colspan=2>' + rshIcons[2] +
                '<select id="selRSHDash" class="rsh-dropbar-panel" style="border-color: #B4B4B4"></select></td>' +
                '</tr><tr>' +
                '<td style="vertical-align: bottom">Dash size</td><td style="vertical-align: bottom">Gap size</td>' +
                '</tr><tr>' +
                '<td>' + rshIcons[4] +
                '<input type="number" id="numRSHDashSizeScale" style="border-radius: 3px; background-color: #EFEFEF;" class="rsh-dropbar-panel" min="0.1" max="10" value="1" step="0.1"  title="Dash length (relative)" disabled \></td>' +
                '<td>' + rshIcons[5] +
                '<input type="number" id="numRSHGapScale" style="border-radius: 3px; background-color: #EFEFEF;" class="rsh-dropbar-panel" min="0.1" max="10" value="1" step="0.1" title="Dash gap size (relative)" disabled \></td>' +
                '</tr></table></td>' +
                '</tr><tr>' +
                '<td>' + rshIcons[0] +
                '<input type="number" id="numRSHScaleWidth" style="border-radius: 3px" class="rsh-dropbar-panel" min="0.1" max="10" value="1" step="0.1" title="Line weight (relative)"></td>' +
                '<td>' + rshIcons[1] +
                '<input type="number" id="numRSHOpacity" style="border-radius: 3px" class="rsh-dropbar-panel" min="0" max="1" value="0.5" step="0.1" title="Color opacity (0–1)"></td>' +
                '</tr><tr>' +
                '<td colspan=2 style="vertical-align: bottom">End cap</td>' +
                '<tr><td colspan=2>' + '' +
                '<select id="selRSHCap" class="rsh-dropbar-panel" style="border-color: #B4B4B4"></select></td>' +
                '</tr></table>' +
                '</div></div>');
        };
        //---------------------------------------
        // Insert UI into side-panel of WME under RSel tab
        setupRSHInterface();
        toggleDropBarMenu(true);
        //---------------------------------------
        // Retrieve any data from saved sessionStorage
        var rsh = checkForAutosave();

        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            highlighterNames = Object.keys(rsh_saved);
        
        for (var k of highlighterNames) {
            document.getElementById("selRSHighlighter").add(new Option(k));
        };

        var d, c, dashSelection = document.getElementById("selRSHDash"),
            capSelection = document.getElementById("selRSHCap"),
            stylesHTML = '';

        for (d = 0; d < rsh.presets.lineAttributes.strokeDashstyle.length; d++) {
            dashSelection.add(new Option(rsh.presets.lineAttributes.strokeDashstyle[
                d]));
        };
        for (c = 0; c < rsh.presets.lineAttributes.strokeLinecap.length; c++) {
            capSelection.add(new Option(rsh.presets.lineAttributes.strokeLinecap[c]));
        };

        for (var styleName in rsh.STYLES) {
            if (!/^Default$/.test(styleName)) {
                stylesHTML += '<li><a name="' + styleName + '">' + styleName + '<span class="fa fa-trash fa-pull-right fa-lg"></span></a></li>';
            }
        }

        $('.rsh-styles-menu ul').append(stylesHTML);
        $('.rsh-styles-menu li>a').click(stylesMenuActions);
        $('.rsh-styles-menu li>a>span.fa-trash').click(function(e){
            e.stopPropagation();
            styleMenuDelete(this);
        });

        return rsh
    }
    //------------------------------------------------------------------------
    rsh = InitRSelHighlights();

    // Update panel
    //if (rsh.loadedSavedSession) {
    rsh.recreateExpressionText();
    updatePanelFromPrefs();
    updateExprMenu();
    //} else {
    //    updatePanelFromPrefs(rsh);
    //}
    rsh_seg = [];

    // Add Road Selector Highlights layer to map
    rsh_OL = initHighlightsLayer();

    //------------------------------------------------------------------------
    // Setup event listeners/triggers
    document.getElementById("selRSHColors").addEventListener('click', function () {
        document.getElementById("selRSHColors").onchange = function () {
            rsh.triggerUpdate = true;
            updatePrefsFromPanel();
            updateHighlighterColorButton();
            requestAnimationFrame(drawHighlights);
            updateExprMenu();
            document.getElementById("selRSHColors").onchange = null;
        };
    }, false);
    //-----
    document.getElementById('btnRSHCount').addEventListener('click', function(e){
        if (document.getElementById('btnRSHmenu').classList.toggle('open')) {
            rsh.storeExpression();
            updateExprMenu();
            setTimeout(function(){window.addEventListener('click',closeExprMenu,false)},100);
        } else {
            setTimeout(function(){window.removeEventListener('click',closeExprMenu,false)},100);
        }
    }, false);
    //--------
    $('#btnRSHighlight').click(drawHighlights);
    $('#btnRSHAdd').click(addNewHighlighter);
    $('#btnRSHSubtract').click(deleteHighlighter);
    $('#btnRSHClearTop').click(eraseHighlight);
    $('#btnRSHClearAll').click(clearAllHighlights);
    $('#icnRSHmoreOpts').click(function () {
        rsh.storeExpression();
        ($('#rshMoreOptions').css('display') === 'none') ? $('#rshMoreOptions').css('display','block'): $('#rshMoreOptions').css('display','none');
    });
    $("#aRSHdropbarLinkHide").click(function(){toggleDropBarMenu(true)});
    $("#aRSHdropbarLinkShow").click(function(){toggleDropBarMenu(false)});
    //--------
    document.getElementById('cbRSHSuppRoad').onclick = function() {
        if (this.checked) localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&s|$/,'&s');//suppress
        else localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&s/,'');
        requestAnimationFrame(Highlight);
    };

    document.getElementById("cbRSHBgHighlight").onclick = function () {
        if (this.checked) localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&t|$/,'&t'); //trace
        else localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&t/,'');
        //rsh.triggerUpdate = true;
        //updatePrefsFromPanel();
        requestAnimationFrame(Highlight);
    };
    //--------
    document.getElementById("selRSHDash").addEventListener('click', function () {
        document.getElementById("selRSHDash").onchange = function () {
            rsh.triggerUpdate = true;
            updatePrefsFromPanel();
            updateDashButtons();
            requestAnimationFrame(Highlight);
            //document.getElementById("selRSHDash").onblur = function () {
                document.getElementById("selRSHDash").onchange = null;
                //document.getElementById("selRSHDash").onblur = null;
            //};
        };
    }, false);
    //--------
    document.getElementById("selRSHCap").addEventListener('click', function () {
        document.getElementById("selRSHCap").onchange = function () {
            rsh.triggerUpdate = true;
            updatePrefsFromPanel();
            requestAnimationFrame(Highlight);
            //document.getElementById("selRSHCap").onblur = function () {
                document.getElementById("selRSHCap").onchange = null;
                //document.getElementById("selRSHCap").onblur = null;
            //};
        };
    }, false);
    //--------
    document.getElementById("numRSHOpacity").onfocusin = function () {
        document.getElementById("numRSHOpacity").onchange = function () {
            rsh.triggerUpdate = true;
            document.getElementById("numRSHOpacity").onblur = function () {
                updatePrefsFromPanel();
                requestAnimationFrame(Highlight);
                document.getElementById("numRSHOpacity").onchange = null;
                document.getElementById("numRSHOpacity").onblur = null;
            };
        };
    };
    //--------
    document.getElementById("numRSHScaleWidth").onfocusin = function () {
        document.getElementById("numRSHScaleWidth").onchange = function () {
            rsh.triggerUpdate = true;
            document.getElementById("numRSHScaleWidth").onblur = function () {
                updatePrefsFromPanel();
                requestAnimationFrame(Highlight);
                document.getElementById("numRSHScaleWidth").onchange = null;
                document.getElementById("numRSHScaleWidth").onblur = null;
            };
        };
    };
    //--------
    document.getElementById("numRSHDashSizeScale").onfocusin = function () {
        document.getElementById("numRSHDashSizeScale").onchange = function () {
            rsh.triggerUpdate = true;
            document.getElementById("numRSHDashSizeScale").onblur = function () {
                updatePrefsFromPanel();
                requestAnimationFrame(Highlight);
                document.getElementById("numRSHDashSizeScale").onchange = null;
                document.getElementById("numRSHDashSizeScale").onblur = null;
            };
        };
    };
    //--------
    document.getElementById("numRSHGapScale").onfocusin = function () {
        document.getElementById("numRSHGapScale").onchange = function () {
            rsh.triggerUpdate = true;
            document.getElementById("numRSHGapScale").onblur = function () {
                updatePrefsFromPanel();
                requestAnimationFrame(Highlight);
                document.getElementById("numRSHGapScale").onchange = null;
                document.getElementById("numRSHGapScale").onblur = null;
            };
        };
    };

    document.getElementById('selRSHighlighter').onchange = function() {
        if (this.value !== null && this.value !== '') {
            $('#applyRSHighlighter').css('color','#2196F3');
        }
    };

    document.getElementById('applyRSHighlighter').onclick = function() {
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            highlighterName = document.getElementById("selRSHighlighter").value;

        $('#applyRSHighlighter').css('color','');
        RSelExprParser.rselButtons.clear();
        rsh.importHighlights(rsh_saved[highlighterName]);
        drawHighlights();
    };

    document.getElementById('saveRSHighlighter').onclick = function() {
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            selectedName = document.getElementById("selRSHighlighter").value;

        var highlighterName = prompt('Please enter a name for this highlighter:', selectedName),
            newOpt = document.createElement('option');

        if (highlighterName !== null && highlighterName !== '') {
            rsh_saved[highlighterName] = {HIGHLIGHTS: rsh.HIGHLIGHTS, rulesStack: rsh.rulesStack};
            localStorage.RSHighlights = JSON.stringify(rsh_saved);

            $('#selRSHighlighter').map(function(i, o) {
                if (new RegExp('^' + highlighterName + '$').test(o.value)) return i
            }).each(function(_, i) {
                document.getElementById("selRSHighlighter").options.remove(i);
            })

            newOpt.appendChild(document.createTextNode(highlighterName));
            document.getElementById("selRSHighlighter").appendChild(newOpt);
            document.getElementById("selRSHighlighter").value = highlighterName;
        }
    };

    document.getElementById('delRSHighlighter').onclick = function() {
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            higlighterName = document.getElementById("selRSHighlighter").value,
            selIdx = document.getElementById("selRSHighlighter").selectedIndex;

        if (selIdx) { //prevent the first option from being deleted
            document.getElementById("selRSHighlighter").removeChild(document.getElementById("selRSHighlighter").options[selIdx]);
            delete rsh_saved[higlighterName];
            document.getElementById("selRSHighlighter").selectedIndex = 0;
            localStorage.RSHighlights = JSON.stringify(rsh_saved);
        }
    };

    document.getElementById('importRSHighlighter').onclick = function(){
        var str = prompt('Paste exported RSel Highlights string to import highlighting rules:'),
            rsh_import, rsh_saved, msgStr,
            htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -44px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: white;font-size: 10px;padding: 5px;">';

        if (str !== null && str !== '') {
            rsh_import = JSON.parse(str);
            if (rsh_import.constructor === Object) {
                for (var r in rsh_import) { //do a quick check
                    if (!rsh_import[r].HIGHLIGHTS || !rsh_import[r].rulesStack) {
                        htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -44px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: red;font-size: 10px;padding: 5px;">',
                        msgStr = 'Import unsuccessful. The pasted string did not contain the expected data.';
                        setTimeout(function(){$('#importRSHighlighter').append(htmlStr + msgStr + '</div>')}, 300);
                        setTimeout(function(){$('#rshMenuNote').remove()}, 3000);
                        //alert('Import unsuccessful. The pasted string did not contain the expected data.');
                        return 1;
                    }
                }
                rsh_saved = JSON.parse(localStorage.RSHighlights);
                $.extend(true, rsh_saved, rsh_import);
                var numImported = Object.keys(rsh_import).length;
                console.log(rsh_saved);
                msgStr = 'Imported ' + numImported + ' sets of highlighters';
                setTimeout(function(){$('#importRSHighlighter').append(htmlStr + msgStr + '</div>')}, 300);
                setTimeout(function(){$('#rshMenuNote').remove()}, 3000);

                //localStorage.RSHighlights = JSON.stringify(rsh_saved);
            } else {
                htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -44px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: red;font-size: 10px;padding: 5px;">',
                msgStr = 'Import unsuccessful. The pasted string did not contain the expected data.';
                setTimeout(function(){$('#importRSHighlighter').append(htmlStr + msgStr + '</div>')}, 300);
                setTimeout(function(){$('#rshMenuNote').remove()}, 3000);
            }
        }
    };

    document.getElementById('exportRSHighlighter').onclick = function(){
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            highlighterName=document.getElementById("selRSHighlighter").value,
            htmlStr, msgStr;

        if (rsh_saved[highlighterName]) {
            htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -44px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: white;font-size: 10px;padding: 5px;">',
            msgStr = '"' + highlighterName + '" was copied to your clipboard';
            copyToClipboard('{"' + highlighterName + '":' + JSON.stringify(rsh_saved[highlighterName]) + '}');
        } else {
            htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -44px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: red;font-size: 10px;padding: 5px;">',
            msgStr = "Select a saved highlighter to export"
        }

        setTimeout(function(){$('#exportRSHighlighter').append(htmlStr + msgStr + '</div>')}, 300);
        setTimeout(function(){$('#rshMenuNote').remove()}, 3000);
    };

    document.getElementById('exportAllRSHighlighter').onclick = function(){
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -46px;white-space: normal;background-color: rgba(0,0,0,0.85);border-radius: 5px;color: white;font-size: 10px;padding: 5px;">';

        copyToClipboard(JSON.stringify(rsh_saved));

        setTimeout(function(){$('#exportAllRSHighlighter').append(htmlStr + 'Data for all saved highlighters were copied to your clipboard</div>')}, 300);
        setTimeout(function(){$('#rshMenuNote').remove()}, 3000);
    };
    //------------------------------------------------------------------------
    window.addEventListener("beforeunload", function () {
        //rsh.HIGHLIGHTS[rsh.lastIdx].rule = {};
        //rsh.housekeeping();
        //rsh.idx = rsh.lastIdx;
        sessionStorage.RSHighlights = JSON.stringify(rsh);
    }, false);

    var timeoutCanceller1, timeoutCanceller2;
    // Event listeners to redraw highlights
    Waze.map.events.register("zoomend", Waze.map, function () {
        //rsh_OL.adjustBounds(W.model.segments.currentDataBounds);
        clearTimeout(timeoutCanceller1);
        clearTimeout(timeoutCanceller2);

        Highlight(true);
        timeoutCanceller1 = setTimeout(function(){
            Highlight();
            timeoutCanceller2 = setTimeout(function(){
                Highlight(null);
            }, 1000);
        }, 1000)
    });

    Waze.map.events.register("moveend", Waze.map, function () {
        clearTimeout(timeoutCanceller1);
        clearTimeout(timeoutCanceller2);

        Highlight(true);
        timeoutCanceller1 = setTimeout(function(){
            Highlight();
            timeoutCanceller2 = setTimeout(function(){
                Highlight(null);
            }, 1000);
        }, 1000)
    });

    Waze.model.actionManager.events.register("afterundoaction", null, function(evt) {
        evt.object.actions.map(function(a){
            if (a.changedSegStates) {
                a.changedSegStates.map(function(segID) {
                    rsh_OL.destroyFeaturesBy('id',segID);
                });
                Highlight();
            }
        });
    });

    Waze.map.segmentLayer.events.register('featuremodified', W.model.segments.objects, function(evt){
        var segID = evt.feature.model.attributes.id;
        rsh_OL.destroyFeaturesBy('id',segID);
        Highlight();
    });
    //Waze.map.events.register("move", Waze.map, Highlight);
    //Waze.map.events.register("changelayer", Waze.map, Highlight);
    //Waze.map.events.register("visibilitychanged", Waze.map, Highlight);
    //------------------------------------------------------------------------

    setTimeout(Highlight,5000);
}

/*//////////////////////////////////////////////////////////////////////////*/
var waitForWMERSel = function () {
    var waitCount = 0,
        tryInitRSH = function () {
            try {
                if (typeof unsafeWindow !== 'undefined' && unsafeWindow.RoadSelector) {
                    /* unsafeWindow.RoadSelector:
                    		checkSegment(expression, segment)
                    		getCurrentExpression()
                    		getExpressionText(expression)
                    		getSavedNames()
                    		getSavedExpression(name)
                    */
                    RSel = unsafeWindow.RoadSelector;
                    RSelHighlights(RSel);

                } else {
                    if (waitCount++ < 35) {
                        setTimeout(tryInitRSH, 600);
                    } else {
                        console.error(
                            'WME RSel Highlights: Could not link up with WME Road Selector.');
                    }
                }
            } catch (err) {
                console.error(err)
            }
        };
    tryInitRSH();
};

setTimeout(waitForWMERSel, 500);