WME Road Selector Highlights

Create custom highlighters to colorize segments according to your selection criteria. Requires WME Road Selector to function.

目前为 2016-03-16 提交的版本。查看 最新版本

// ==UserScript==
// @name            WME Road Selector Highlights
// @namespace       https://greasyfork.org/users/11629-TheLastTaterTot
// @version         0.7.7.6
// @description     Create custom highlighters to colorize segments according to your selection criteria. 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=113467
// @run-at          document-end
// ==/UserScript==
//---------------------------
// 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)
//
// DEBUG:
//RSelEP = RSelExprParser;
//rsh = {};
//---------------------------

var RSel, rsh, rsh_seg, rsh_OL, rsh_OLu,
    isFirefox = /\bfirefox\b/i.test(navigator.userAgent),
    stylesVersion = 1,
    userBgHighlight, userEditableOnly, userSuppressRoad, zoomLevel;

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: [3, 5, 7, 8, 9, 10.4, 13, 14, 14, 14, 14],
            strokeDashstyle: ["solid", "- - - - -", "– ‧ – ‧"],
            strokeDasharray: [
                ["solid"],
                [1, 1],
                [2, 1, 1, 1]
            ],
            strokeLinecap: ["butt", "round", "square"],
            underRoads: false
        },
        lineStyles: {
            Default: {
                strokeColor: "#FFFF00",
                strokeOpacity: 0.5,
                strokeDashstyle: 0,
                strokeLinecap: 0,
                strokeWidthScale: 1,
                strokeDashSizeScale: 1,
                strokeGapScale: 1
            },
            "Small Dots": {
                strokeColor: "#FFFF00",
                "strokeOpacity": 0.8,
                "strokeDashstyle": 1,
                "strokeLinecap": 1,
                "strokeWidthScale": 0.4,
                "strokeDashSizeScale": 0.1,
                "strokeGapScale": 2.4
            },
            "Large Dots": {
                strokeColor: "#FFFF00",
                "strokeOpacity": 0.5,
                "strokeDashstyle": 1,
                "strokeLinecap": 1,
                "strokeWidthScale": 1.5,
                "strokeDashSizeScale": 0.01,
                "strokeGapScale": 1.5
            },
            "Fuzzy Lines": {
                strokeColor: "#FFFF00",
                "strokeOpacity": 0.8,
                "strokeDashstyle": 1,
                "strokeLinecap": 0,
                "strokeWidthScale": 1,
                "strokeDashSizeScale": 0.1,
                "strokeGapScale": 0.1
            },
            Ticks: {
                strokeColor: "#FFFF00",
                "strokeOpacity": 0.7,
                "strokeDashstyle": 1,
                "strokeLinecap": 0,
                "strokeWidthScale": 1,
                "strokeDashSizeScale": 0.2,
                "strokeGapScale": 0.3
            }
        },
    };

    this.STYLES = this._clone(this.presets.lineStyles);

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

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

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

    this.lastIdx = 0;
    this.idx = 0;
    this.rulesStack = [0];
    this.prevZoom = null;
    this.doNotDrawRshIdx = true;
    this.triggerUpdate = true;
    this.showSessionHighlights = false;
    this.loadedSavedSession = false;
    this.stylesVersion = 1;
    this.highlighterName = undefined;

    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.HIGHLIGHTS[idx].rule.tryOnce = false;
    };

    this.storeCurrentExpression = function(idx) {
        var force = false;
        switch (true) {
            case idx:
                force = true;
            case !idx:
                idx = this.idx;
                break;
        }

        if (force || this.HIGHLIGHTS[idx].rule.isValidated === true ||
            (this.HIGHLIGHTS[idx].rule.isValidated === null && !this.doNotDrawRshIdx)) {
            var currExpr = RSel.getCurrentExpression();

            this.HIGHLIGHTS[idx].rule = {
                expr: (currExpr) ? currExpr : {},
                text: (currExpr) ? RSel.getExpressionText(currExpr) : null,
                isValidated: this.HIGHLIGHTS[idx].rule.isValidated,
                tryOnce: false //note that once saved, tryOnce is always set to false... thus, if it is true, that means it failed the second time.
            };
        } //else { console.debug('Expression parsing was invalid. Expression not stored.')}
    };

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

        if (idx === this.idx) { //save whatever expression is active in RSel
            if (this.HIGHLIGHTS[this.idx].rule.isValidated === false) {
                if (this.HIGHLIGHTS[this.idx].rule.tryOnce === true &&
                        document.getElementById('rshExprWarn') === null) {
                    this.storeCurrentExpression(true); //force save
                }
            //} else if (!this.HIGHLIGHTS[this.idx].rule.text) {
            //    this.storeCurrentExpression();
            }
        }

        if (rsh.HIGHLIGHTS[idx].rule.text) {
            //console.debug(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.storeCurrentExpression(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 id="rshExprWarn" 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>';
                }
            }
        } // else nothing saved to validate
        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;

        var exprIsValidated = this.validateExprText(idx);
        if (exprIsValidated) {
            return this.HIGHLIGHTS[idx].rule.text;
        //} else if (exprIsValidated === null) { //empty
        //    return RSel.getExpressionText(rsh.HIGHLIGHTS[idx].rule.expr);
        } else {
            this.HIGHLIGHTS[this.idx].rule.tryOnce = true; //try again
            if (this.validateExprText(idx)) { // -- this time alllowing expr save/update if no parse warning is present in DOM
                return this.HIGHLIGHTS[idx].rule.text; //new expression text
            } else if (this.HIGHLIGHTS[idx].rule.text) { //resort to using
                return RSel.getExpressionText(rsh.HIGHLIGHTS[idx].rule.expr);
            } else {
                return null;
            }
        }
    };

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

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

    this.deleteHighlighter = function(idx) {
        if (idx === undefined) idx = this.idx;
        //RSelExprParser.rselButtons.clear();

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

    this.importHighlights = function(addThis) {
        this.HIGHLIGHTS = addThis.HIGHLIGHTS;
        this.rulesStack = this._seq(this.HIGHLIGHTS.length);
        this.lastIdx = addThis.HIGHLIGHTS.length - 1;
        this.idx = this.lastIdx;
        this.triggerUpdate = true;
        this.loadedSavedSession = true;
        this.showSessionHighlights = !!addThis.showSessionHighlights;
        this.doNotDrawRshIdx = !!addThis.doNotDrawRshIdx;
        this.highlighterName = addThis.highlighterName;
        this.HIGHLIGHTS.forEach(function(a){a.rule.tryOnce = false;})
    };

    this.addStyles = function() {
        try {
            var rshStyles = JSON.parse(localStorage.RSHighlights_Styles);

            if (rshStyles.constructor === Object) {
                if (this.stylesVersion !== stylesVersion) {
                    $.extend(true, rshStyles, this.presets.lineStyles);
                    this.STYLES = rshStyles;
                    this.stylesVersion = stylesVersion;
                    localStorage.RSHighlights_Styles = JSON.stringify(this.STYLES);
                } else {
                    this.STYLES = rshStyles;
                }
            }
        } catch (err) { /* ignore */ }
    };

    this.getCurrentExprText = RSelExprParser.getCurrentExprText;
}

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

/*////////////////////////////////////////////////////////////////////////////*/
//
//  TODO: Organize code...convert some functions to methods and prototypes
//
function RSelHighlights() {
    var updatePrefsFromPanel = function() {
        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.HIGHLIGHTS[rsh.idx].style.underRoads = document.getElementById('cbRSHunderRoads').checked;
        rsh.storeCurrentExpression();
    };

    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";
        }
    };

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

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

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

    var updatePanelFromPrefs = function() {
        //console.debug('updatePanelFromPrefs()')

        //rsh.storeCurrentExpression(); //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();
        document.getElementById('cbRSHunderRoads').checked = rsh.HIGHLIGHTS[rsh.idx].style.underRoads;

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

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

        updatePrefsFromPanel();

        // adjust some UI stuff for enhancing usability
        /*
        if (!!document.getElementById('selRSHighlighter').value && document.getElementById('selRSHighlighter').value !== '') {
        document.getElementById('saveRSHighlighter').style.color = "#ed503b";
        }*/

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

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

            while (mm--) {
                if (rsh.HIGHLIGHTS[mm].style.underRoads === undefined) rsh.HIGHLIGHTS[mm].style.underRoads = false;
                // Stroke width (line thickness)
                var separationAmt = parseInt((strokeWidthAtZoom) / numHighlights),
                    strokeWidthScale = rsh.HIGHLIGHTS[mm].style.strokeWidthScale + (underRoadsScaler * rsh.HIGHLIGHTS[mm].style.underRoads),
                    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;
    };

    // ---------------------------------------------------------------------
    // Use this function if there is a chance that layers or highlighters may be set to off
    // ...this will reset them. If that is not necessary, call Highlights(optionalArg) directly instead.
    function drawHighlights(ev,optionalArg) {
        rsh.doNotDrawRshIdx = false;
        //console.debug(ev,optionalArg);
        if (optionalArg === 'import') {
            optionalArg = true;

        } else {
            var currExpr = RSel.getCurrentExpression();
            if (currExpr !== null) {
                rsh.storeCurrentExpression(true); //force save
            }
        }
        requestAnimationFrame(updateExprMenu);

        if (rsh.HIGHLIGHTS.length) {
            if (optionalArg === undefined) {
                optionalArg = rsh.idx;
            }

            if (rsh.HIGHLIGHTS[rsh.idx].rule.expr &&
                Object.keys(rsh.HIGHLIGHTS[rsh.idx].rule.expr).length === 0) {
                rsh.doNotDrawRshIdx = true;
            } else if (rsh.HIGHLIGHTS[rsh.idx].rule.isValidated === false) {
                if (document.getElementById('rshExprWarn') === null)
                    RSelExprParser.rselButtons.clear();
            }

            rsh.showSessionHighlights = true;
            rsh_OL.setVisibility(true);
            rsh_OLu.setVisibility(true);

            disableEraseButton(false);

            requestAnimationFrame(function() {
                Highlight(optionalArg); //if rsh.triggerUpdate, this will also be assoc with an expr save
                sessionStorage.RSHighlights = JSON.stringify(rsh);
            });
        }
    }

    var eraseHighlight = function(idx) {
        if (idx === undefined) idx = rsh.idx;

        rsh.doNotDrawRshIdx = true;
        rsh.clearStoredExpression();

        if (!rsh.HIGHLIGHTS[idx].style.underRoads)
            rsh_OL.destroyFeaturesByMapKey(idx);
        else rsh_OLu.destroyFeaturesByMapKey(idx);

        updateExprMenu();
        requestAnimationFrame(Highlight);
        disableEraseButton(true);
    }

    function clearAllHighlights() {
        var confirmDelete = confirm("Are you sure you want to clear all highlighting rules?");
        if (confirmDelete) {
            //RSelExprParser.rselButtons.clear();
            rsh = new RSH();
            rsh.triggerUpdate = true;
            rsh.doNotDrawRshIdx = true;
            rsh_OL.destroyAllFeatureMaps();
            rsh_OLu.destroyAllFeatureMaps();
            rsh_OL.setVisibility(false);
            rsh_OLu.setVisibility(false);

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

            sessionStorage.RSHighlights = null;
        }
    }

    // ---------------------------------------------------------------------
    function addNewHighlighter() {
        var currExpr = RSel.getCurrentExpression(),
            clearExpr = false;

        if (document.getElementById('rshExprWarn')) clearExpr = true;

        if (rsh.HIGHLIGHTS[rsh.idx].rule.text === null || rsh.HIGHLIGHTS[rsh.idx].rule.text.length === 0) {
            // add rule to current empty index and then adds a new index
            if (currExpr !== null) drawHighlights([],rsh.idx)
            rsh.addHighlighter();
            rsh.doNotDrawRshIdx = true;
            updateExprMenu(); // -- also updates panel with settings from rsh.HIGHLIGHTS[].style
            sessionStorage.RSHighlights = JSON.stringify(rsh);
            if (clearExpr) RSelExprParser.rselButtons.clear();
        } else {
            // adds an empty index and then stores the current selection rule into new index if currExpr is not empty
            rsh.addHighlighter();
            rsh.doNotDrawRshIdx = true;
            //rsh.storeCurrentExpression(true);
            updateExprMenu(); // -- also updates panel with settings from rsh.HIGHLIGHTS[].style
            sessionStorage.RSHighlights = JSON.stringify(rsh);
            if (clearExpr) RSelExprParser.rselButtons.clear();
        }
    }

    var deleteHighlighter = function(idx) {
        if (idx === undefined) idx = rsh.idx;
        if (rsh.lastIdx > 0) {
            //var confirmDelete = confirm("Are you sure you want to delete the selected highlighter?");
            //if (confirmDelete) {
                if (!rsh.HIGHLIGHTS[idx].style.underRoads)
                    rsh_OL.destroyFeaturesByMapKey(idx);
                else rsh_OLu.destroyFeaturesByMapKey(idx);

                rsh.deleteHighlighter(idx);
                //updatePanelFromPrefs();
                updateExprMenu(true); // -- also updates panel with settings from rsh.HIGHLIGHTS[].style

                sessionStorage.RSHighlights = JSON.stringify(rsh);
            //}
        } else {
            eraseHighlight(idx);
        }
    };

    var exportRSelHighlighter = function(ev, str) {
        var rsh_saved, highlighterName, htmlStr, msgStr, uniqueName, style;

        if (str === undefined) {
            rsh_saved = JSON.parse(localStorage.RSHighlights);
            highlighterName = document.getElementById("selRSHighlighter").value;
            exportStr = '{"' + highlighterName + '":' + JSON.stringify(rsh_saved[highlighterName]).replace(/("#[\w\d]{6})\s?/ig, '$1') + '}';
            style = 'position: absolute; bottom: 110%; width: 100px; left: -44px;';
        } else {
            var dateName = new Date(),
                d = 'Highlight ' + dateName.getFullYear()+ '-' + dateName.getMonth()+ '-' + dateName.getDate() + ' ' + dateName.getHours() + '.' + dateName.getMinutes() + '.' + dateName.getSeconds();

            highlighterName = prompt('Please provide a unique name for the highlighter', d);
            if (highlighterName === null) {
                highlighterName = d;
            }
            exportStr = '{"' + highlighterName + '":{"HIGHLIGHTS":[' + JSON.stringify(rsh.HIGHLIGHTS[rsh.idx]).replace(/("#[\w\d]{6})\s?/ig, '$1') + ']}}';
            rsh_saved = {};
            rsh_saved[highlighterName] = true;
            style = 'position: absolute; width: 120px; bottom: 25%; left: 40%;';
        }

        if (rsh_saved[highlighterName]) {
            htmlStr = '<div id="rshMenuNote" style="' + style + '">';
            msgStr = '"' + highlighterName + '" was copied to clipboard';
            copyToClipboard(exportStr);
        } else {
            htmlStr = '<div id="rshMenuNote" style="' + style + '">';
            msgStr = "Select a highlighter to export";
            document.getElementById('selRSHighlighter').focus();
        }

        setTimeout(function() {
            $(ev.target).append(htmlStr + msgStr + '</div>')
        }, 300);
        setTimeout(function() {
            $('#rshMenuNote').remove()
        }, 5000);
        document.getElementById('rshMenuNote').onclick = function(){this.remove()};
    };

    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    var findNodeIndex = function(attr, val) {
        for (var i = 0, iLength = this.length; i < iLength; i++) {
            if (this[i].querySelector('[' + attr + '="' + String(val) + '"]')) return i;
        }
        return null;
    };

    var dragEndExpr = function(ev) {
        //$(this.children).each(function(i,node){node.style.opacity = 1;});
        //console.debug('DragEnd:',ev.target)

        $('div.rsh-expr-item').css('opacity', '');
        $('div.rsh-expr-item').prop('draggable', false);
        $('.rsh-expr-menu').css('pointer-events', 'auto');
        $('.rsh-expr-menu').removeClass('rsh-dragging');
        $('div.rsh-expr-item').removeClass('rsh-drag');
        //$('.rsh-expr-dragicn').each(function(i,node){node.style.pointerEvents = 'auto';});
        //$('div.rsh-expr-item').each(function(i,node){node.removeEventListener('dragstart',dragStartExpr,false)});
        $('.rsh-expr-text').each(function(i, node) {
            node.removeEventListener('dragover', allowDropExpr, false)
        });
        $('.rsh-expr-text').each(function(i, node) {
            node.removeEventListener('dragenter', dragEnterExpr, false)
        });
        $('div.rsh-expr-item').each(function(i, node) {
            node.removeEventListener('drop', dropExpr, false)
        });
        $('div.rsh-expr-item').each(function(i, node) {
            node.removeEventListener('dragend', dragEndExpr, false)
        });
        window.removeEventListener('mouseup', dragEndExpr, false);
    };

    var dropExpr = function(ev) {
        //console.debug('Drop:',this);

        if (ev.preventDefault()) ev.preventDefault();
        //ev.stopPropagation();

        var exprListEl = document.getElementsByClassName('rsh-expr-menu')[0],
            draggedExprID = ev.dataTransfer.getData('text'),
            draggedExprEl = document.querySelector('.rsh-expr-item[value="' + draggedExprID + '"]').parentNode,
            targetExprID = this.getAttribute('value'),
            refNode = exprListEl.childNodes[findNodeIndex.call(exprListEl.childNodes, 'value', targetExprID)];

        exprListEl.insertBefore(draggedExprEl, refNode);
        exprListEl.insertBefore(refNode, draggedExprEl);

        //$('.rsh-expr-item:not([value="' + draggedExprID + '"]>.rsh-expr-text)').each(function(i,node){node.removeEventListener('drop',dropExpr,false)});

        var draggedExprIdx = parseInt(draggedExprID),
            targetExprIdx = parseInt(targetExprID),
            shiftIdx = 0,
            rulesLength = rsh.rulesStack.length,
            rulesIndex = rsh._seq(rulesLength);


        if ((draggedExprIdx !== targetExprIdx) && (draggedExprIdx + 1 !== targetExprIdx)) {
            //console.debug('Expression inserted at position',targetExprIdx);
            var draggedExpr = rsh.HIGHLIGHTS[draggedExprIdx];

            if (targetExprIdx !== 100) {
                if (draggedExprIdx > targetExprIdx) shiftIdx = 1; //moved to earlier pos in array

                rsh.HIGHLIGHTS.splice(targetExprIdx, 0, draggedExpr); //inert expression after target position
                rsh.HIGHLIGHTS.splice(draggedExprIdx + shiftIdx, 1); //remove expression from previous position
                rulesIndex.splice(targetExprIdx, 0, draggedExprIdx);
                rulesIndex.splice(draggedExprIdx + shiftIdx, 1);
                rsh.rulesStack.splice(targetExprIdx, 0, draggedExprIdx);
                rsh.rulesStack.splice(draggedExprIdx + shiftIdx, 1);
            } else {
                rsh.HIGHLIGHTS.push(draggedExpr);
                rsh.HIGHLIGHTS.splice(draggedExprIdx, 1); //remove expression from previous position
                rulesIndex.push(draggedExprIdx);
                rulesIndex.splice(draggedExprIdx, 1);
                rsh.rulesStack.push(draggedExprIdx);
                rsh.rulesStack.splice(draggedExprIdx, 1);
            }

            rsh.idx = rulesIndex.findIndex(function(r) {
                if (r === rsh.idx) return true
            });
            rsh.rulesStack = rsh.rulesStack.map(function(a, i) {
                return (a[i] !== false) ? i : false
            });
            document.getElementById('btnRSHmenu').click(); //fix this... why separate eventlisteners?
            document.getElementById('btnRSHCount').click(); //fix this... why separate eventlisteners?
            rsh.triggerUpdate = true;
            requestAnimationFrame(function() {
                Highlight(true)
            });
        } else {
            console.debug('Not inserted after expression', targetExprID);
        }
    };

    var allowDropExpr = function(ev) {
        if (ev.preventDefault()) ev.preventDefault();
        ev.dataTransfer.dropEffect = 'move';
        return false;
    };

    var dragEnterExpr = function(ev) {
        //console.debug('DragEnter:',ev.target);
        $('div.rsh-expr-item:not([value="' + this.parentNode.value + '"])').removeClass('rsh-drag');
        this.parentNode.classList.add('rsh-drag');
    };

    var dragStartExpr = function(ev) {
        //console.debug('DragStart:',this);

        var draggedExprID = this.getAttribute('value');
        //console.debug('Dragging expression',parseInt(draggedExprID)+1);

        ev.dataTransfer.setData('text', draggedExprID);
        $('div.rsh-expr-item:not([value="' + draggedExprID + '"]>.rsh-expr-text)').each(function(i, node) {
            node.addEventListener('drop', dropExpr, false)
        });
        $('.rsh-expr-menu').css('pointer-events', 'none');
        $('.rsh-expr-menu').addClass('rsh-dragging');
        //$('.rsh-expr-dragicn').each(function(i,node){node.style.pointerEvents = 'none';});
        //$(this.children).each(function(i,node){node.style.opacity = 0.2;});
        this.style.opacity = 0.4;
    };

    function updateExprMenu(doExpr) {
        //console.debug('updateExprMenu()')
        // Dropup menu for selecting layers
        var e = rsh.HIGHLIGHTS.length,
            htmlText = '<li class="rsh-hidden"><div value="100" class="rsh-expr-item"><span class="rsh-expr-text"></span></div></li>',
            exprText, selStatus;

        while (e-- > 0) {
            if (e === rsh.idx) {
                selStatus = ' active';
                if (doExpr) exprText = rsh.recreateExpressionText(e);
                else exprText = RSel.getExpressionText(rsh.HIGHLIGHTS[e].rule.expr);
            } else {
                selStatus = '';
                exprText = RSel.getExpressionText(rsh.HIGHLIGHTS[e].rule.expr);
            }
            if (!exprText) {
                exprText = '&nbsp;';
                //if (e === rsh.idx) RSelExprParser.rselButtons.clear();
            } else {
                exprText += '<i title="Export highlighter" class="rsh-expr-export fa fa-share-square-o fa-fw"></i>';
            }

            htmlText += '<li><div value="' + e + '" ' +
                'class = "rsh-expr-item' + selStatus + '">' +
                '<span class="rsh-expr-del fa fa-times"></span><span class="fa fa-square rsh-expr-color fa-pull-left" style="color:' +
                rsh.HIGHLIGHTS[e].style.strokeColor + '"></span>' +
                '<span class="rsh-expr-text">' + exprText +
                '<span class="rsh-expr-spacer"></span></span>' +
                '<span class="rsh-expr-dragicn"><i class="fa fa-ellipsis-v" style="padding: 1px;"></i><i class="fa fa-ellipsis-v"></i></span>' +
                '</div></li>';
        }

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

        //------
        if (rsh.HIGHLIGHTS[rsh.idx].rule.expr &&
            Object.keys(rsh.HIGHLIGHTS[rsh.idx].rule.expr).length === 0)
            rsh.doNotDrawRshIdx = true;
        else rsh.doNotDrawRshIdx = false;

        $('div.rsh-expr-item').click(function(e) {
            if (rsh.triggerUpdate) Highlight(); //do not make async
            rsh.idx = parseInt(this.getAttribute('value'));
            //console.debug(this); console.debug(e);
            updateExprMenu(true); //updates expression in RSel as well as saves it in a highlighter slot
            disableEraseButton();
            if (!rsh.HIGHLIGHTS[rsh.idx].rule.text) RSelExprParser.rselButtons.clear();
        });

        $('.rsh-expr-export').click(function(e) {
            e.stopPropagation();
            exportRSelHighlighter(e, true);
        });

        $('.rsh-expr-del').click(function(e) {
            e.stopPropagation();
            //console.debug(this);
            var idx = parseInt(this.parentNode.getAttribute('value'));
            deleteHighlighter(idx)
            setTimeout(function(){document.getElementById('btnRSHCount').click()},24);
        });

        $('div.rsh-expr-item').each(function(i, node) {
            node.addEventListener('dragstart', dragStartExpr, false)
        });
        $('.rsh-expr-dragicn').each(function(i, node) {
            node.addEventListener('mousedown', function(e) {
                //console.debug('hi');
                e.stopPropagation();
                $('div.rsh-expr-item').prop('draggable', true);
                $('.rsh-expr-text').each(function(i, node) {
                    node.addEventListener('dragover', allowDropExpr, false)
                });
                $('.rsh-expr-text').each(function(i, node) {
                    node.addEventListener('dragenter', dragEnterExpr, false)
                });
                $('div.rsh-expr-item').each(function(i, node) {
                    node.addEventListener('dragend', dragEndExpr, false)
                });
                window.addEventListener('mouseup', dragEndExpr, false);
            }, 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 = "#DAEBF1";
            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,
            redrawFeats = false,
            recheckRules = false, //Note: for both recheck and redraw, remove the featureMapping...
            lineStylesUpdate = rsh.triggerUpdate;

        if (rsh_OL.visibility) {
            if (lineStylesUpdate) {
                setupLineStyles();
                //rsh_OL.destroyFeaturesByMapKey(rsh.idx);
                //checkAllSegs = true;
            } //      Pass Boolean true or rsh.idx. TODO: Allow specifying rsh.idx

            var 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, //***********
                ids = Object.keys(segments),
                highlighterRuleIndexes,
                newFeatureArray = [], newFeatureArrayu = [],
                newBgFeatureArray = [],
                delFeatureArray = [], delFeatureArrayu = [],
                delBgFeat, seg, highlightIsTrue, countHighlighted, redraw, featureExists;

            if (rsh.doNotDrawRshIdx) {
                if (userSuppressRoad) optionalArg = true; //must redraw because highlight style with this setting enabled might change depending on which highlight rules are active
                highlighterRuleIndexes = rsh.rulesStack.filter(function(a) {
                    return a !== rsh.idx
                });
                rsh_OL.destroyFeaturesByMapKey(rsh.id);
                rsh_OLu.destroyFeaturesByMapKey(rsh.idx);
            } else {
                highlighterRuleIndexes = rsh.rulesStack;
            }

            if (!userBgHighlight)
                requestAnimationFrame(function() {
                    rsh_OL.destroyFeaturesByMapKey('bg')
                });

            if (optionalArg === true) { //rechecks and redraws everything
                rsh_OL.destroyAllFeatureMaps();
                rsh_OLu.destroyAllFeatureMaps();
                //console.debug('Reset requested: rsh_OL.destroyAllFeatureMaps();')
            } else {
                if (zoomLevel !== currentZoom) {
                    redrawFeats = highlighterRuleIndexes.map(function(a) {return a});
                    rsh_OL.destroyFeatures();
                    rsh_OLu.destroyFeatures();
                }

                if (optionalArg === null) { //checks all available segs – not just the ones on-screen, but does not recheck rules nor redraws
                    checkAllSegs = true;
                } else if (optionalArg === 'd') { //does not recheck rules, but redraws all already existing highlights
                    redrawFeats = highlighterRuleIndexes.map(function(a) {return a});
                     //  (e.g., for line style changes, changes to seg geometry, ZOOM CHANGES)
                    rsh_OL.destroyFeatures(); //---destroy all features, but don't remove featureMapping
                    rsh_OLu.destroyFeatures();
                } else if (optionalArg === 'a') { //recheck all the rules, but does not redraw if no assignment change
                    recheckRules = true; //  (e.g., for rule changes or changes to seg properties other than geometry & style)
                    checkAllSegs = true;
                } else if (optionalArg &&
                    optionalArg.constructor === Number &&
                    rsh.rulesStack.contains(optionalArg)) { //recheck and redraw all for specified rsh.idx

                    //if (lineStylesUpdate) {
                    if (!redrawFeats) redrawFeats = highlighterRuleIndexes.map(function() {
                        return optionalArg
                    });
                    checkAllSegs = true;
                    //} else {
                    rsh_OL.destroyFeaturesByMapKey(optionalArg);
                    rsh_OLu.destroyFeaturesByMapKey(optionalArg);
                    if (userBgHighlight) rsh_OL.destroyFeaturesByMapKey('bg');
                    //}
                } else if (lineStylesUpdate) {
                    if (!redrawFeats) redrawFeats = highlighterRuleIndexes.map(function() {
                        return rsh.idx
                    });
                    checkAllSegs = true;
                    rsh_OL.destroyFeaturesByMapKey(rsh.idx);
                    rsh_OLu.destroyFeaturesByMapKey(rsh.idx);
                    if (userBgHighlight) rsh_OL.destroyFeaturesByMapKey('bg');
                }
            }

            if (!redrawFeats) redrawFeats = highlighterRuleIndexes.map(function() {
                return false
            });

            //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)] = {}
                if (rsh_OLu._featureMap[String(h)] === undefined) rsh_OLu._featureMap[String(h)] = {}
            });

            var s = ids.length,
                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 highlighterRuleIndexes) {
                        if (Object.keys(rsh.HIGHLIGHTS[h].rule.expr).length === 0) {
                            rsh_OL.destroyFeaturesByMapKey(h);
                            rsh_OLu.destroyFeaturesByMapKey(h);
                            redraw = redrawFeats.shift();
                            break;
                        }

                        redraw = redrawFeats.shift();
                        H = String(h);

                        if (!rsh.HIGHLIGHTS[h].style.underRoads) {

                            if (rsh_OL._featureMap[H][ids[s]]) {
                                highlightIsTrue = rsh_OL._featureMap[H][ids[s]].highlight;
                                //featureExists = (rsh_OL._featureMap[H][ids[s]].feature) ? true : false;
                                featureExists = (rsh_OL._featureMap[H][ids[s]].feature && rsh_OL._featureMap[H][ids[s]].feature.id) ? true : false;
                            } else {
                                highlightIsTrue = undefined;
                                featureExists = false;
                            }
                            try {
                                if (highlightIsTrue === undefined) { //if highlight feature has not been drawn nor checked or everything was destroyed...
                                    if ((!userEditableOnly || seg.arePropertiesEditable()) &&
                                        RSel.checkSegment(rsh.HIGHLIGHTS[h].rule.expr, seg)) { //test against selection criteria
                                        countHighlighted++;

                                        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 {
                                        rsh_OL._featureMap[H][ids[s]] = {
                                            highlight: false
                                        };
                                    }

                                } else if (recheckRules) { //just the rules need to be rechecked
                                    if ((!userEditableOnly || seg.arePropertiesEditable()) &&
                                        RSel.checkSegment(rsh.HIGHLIGHTS[h].rule.expr, seg)) { //test against selection criteria
                                        countHighlighted++;
                                        if (!featureExists) { //wasn't highlighted or feature has been destroyed, but now passes rule...
                                            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 { //seg fails selection criteria,
                                        //but it is still drawn...
                                        if (highlightIsTrue) delFeatureArray.push(rsh_OL._featureMap[H][ids[s]].feature);
                                        //delFeatureArray.concat(rsh_OL.getFeaturesByAttribute('rshID', 'h'+h+'-'+ids[s])); //flag it for removal
                                    } //TODO: add in 'false' assignment to highlight property

                                } else if (highlightIsTrue) {
                                    countHighlighted++;

                                    if (redraw === h || !featureExists) { //feature has been deleted by OL
                                        //if (!featureExists) {
                                        if (countHighlighted === 1) {
                                            newFeatureArray.push(rsh_OL.createLineFeature(seg, hlLineStyle[h], h, countHighlighted * userSuppressRoad));
                                        } else {
                                            newFeatureArray.push(rsh_OL.createLineFeature(seg, hlLineStyleOvrlp[h], h));
                                        }
                                        //}

                                        //if (featureExists) { //redraw all previously highlighted segs...
                                        //delFeatureArray.concat(rsh_OL.getFeaturesByAttribute('rshID', 'h'+h+'-'+ids[s])); //flag it for removal
                                        //    delFeatureArray.push(rsh_OL._featureMap[H][ids[s]].feature);
                                        //}
                                            /*if (countHighlighted === 1) { //TODO: Optimize this later...
                                                rsh_OL.redrawLineFeature(rsh_OL._featureMap[H][ids[s]].feature, hlLineStyle[h], countHighlighted * userSuppressRoad);
                                            } else {
                                                rsh_OL.redrawLineFeature(rsh_OL._featureMap[H][ids[s]].feature, hlLineStyleOvrlp[h]);
                                            }
                                        }*/
                                    }
                                }
                            } catch (err) {}
                        } else {
                            if (rsh_OLu._featureMap[H][ids[s]]) {
                                highlightIsTrue = rsh_OLu._featureMap[H][ids[s]].highlight;
                                //featureExists = (rsh_OL._featureMap[H][ids[s]].feature) ? true : false;
                                featureExists = (rsh_OLu._featureMap[H][ids[s]].feature && rsh_OLu._featureMap[H][ids[s]].feature.id) ? true : false;
                            } else {
                                highlightIsTrue = undefined;
                                featureExists = false;
                            }
                            try {
                                if (highlightIsTrue === undefined) { //if highlight feature has not been drawn nor checked or everything was destroyed...
                                    if ((!userEditableOnly || seg.arePropertiesEditable()) &&
                                        RSel.checkSegment(rsh.HIGHLIGHTS[h].rule.expr, seg)) { //test against selection criteria
                                        countHighlighted++;
                                        newFeatureArrayu.push(rsh_OLu.createLineFeature(seg, hlLineStyle[h], h));
                                    } else {
                                        rsh_OLu._featureMap[H][ids[s]] = {
                                            highlight: false
                                        };
                                    }
                                } else if (recheckRules) { //just the rules need to be rechecked
                                    if ((!userEditableOnly || seg.arePropertiesEditable()) &&
                                        RSel.checkSegment(rsh.HIGHLIGHTS[h].rule.expr, seg)) { //test against selection criteria
                                        countHighlighted++;
                                        if (!featureExists) newFeatureArrayu.push(rsh_OLu.createLineFeature(seg, hlLineStyle[h], h));
                                    } else { //seg fails selection criteria,
                                        //but it is still drawn...
                                        if (highlightIsTrue) delFeatureArrayu.push(rsh_OLu._featureMap[H][ids[s]].feature);
                                    } //TODO: add in 'false' assignment to highlight property
                                } else if (highlightIsTrue) {
                                    countHighlighted++;
                                    if (redraw === h || !featureExists) newFeatureArrayu.push(rsh_OLu.createLineFeature(seg, hlLineStyle[h], h));
                                }
                            } catch (err) {}
                        }
                    }
                    // TODO: add a feature changed check and redraw rather than destroy+draw
                    //--------------------------------------------------------------------------------------
                    // Check if script should highlight "background roads" (i.e., roads that don't meet any of the other highlight criteria)
                    if (userBgHighlight) {
                        if (rsh_OL._featureMap.bg[ids[s]]) {
                            //highlightIsTrue = rsh_OL._featureMap.bg[ids[s]].highlight;
                            featureExists = (rsh_OL._featureMap.bg[ids[s]].feature && rsh_OL._featureMap.bg[ids[s]].feature.id) ? true : false;
                        } else {
                            //highlightIsTrue = undefined;
                            featureExists = false;
                        }

                        if (countHighlighted === 0 && (!featureExists || redraw===true)) {
                            newBgFeatureArray.push(rsh_OL.createLineFeature(seg, bgLineStyle, 'bg'));
                        } else if (countHighlighted !== 0 && featureExists) {
                            requestAnimationFrame(function() {
                                rsh_OL.destroyFeaturesBy('id', parseInt(ids[s]))
                            });
                        }
                    } //------------------------------------------------------------------------------------
                } //isOnScreen
            } //while
            rsh_OL.destroyFeatureArray(delFeatureArray);
            rsh_OLu.destroyFeatureArray(delFeatureArrayu);

            if (newBgFeatureArray.length) //requestAnimationFrame(function() {
                rsh_OL.addFeatures(newBgFeatureArray);
            //});

            if (newFeatureArray.length) rsh_OL.addFeatures(newFeatureArray);
            if (newFeatureArrayu.length) rsh_OLu.addFeatures(newFeatureArrayu);
            rsh_OLu.setZIndex(334);


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

            }
            if (Object.keys(rsh_OL._featureMap.bg).length > 10000) rsh_OL.cleanupFeatureMap('bg');

            zoomLevel = currentZoom;

        } else {
            rsh.showSessionHighlights = false;
        }
    }

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

    var addStuffToLayerObject = function(layer) {
        layer._clone = rsh._clone;

        layer._getFeatureMapCounts = function() {
            var fTotal, fKey, idCounter, id;

            for (fKey in this._featureMap) {
                fTotal = Object.keys(this._featureMap[fKey]).length;
                console.debug('# of unique segs logged in _featureMap["' + fKey + '"] =', fTotal);
                idCounter = 0;
                for (id in this._featureMap[fKey]) {
                    if (this._featureMap[fKey][id].feature && this._featureMap[fKey][id].feature.id) idCounter++;
                }
                console.debug('# of drawn highlights on map from _featureMap["' + fKey + '"] =', idCounter);
            }
            console.debug('Total segments in data extent =', W.map.segmentLayer.features.length);
            console.debug('OL features array length =', this.features.length);
        };

        layer.redrawLineFeature = function(feature, lineOptions, stack) {
            if (stack === undefined) { /* do nothing */ } else if (stack === 1) {
                lineOptions.strokeOpacity = 0.9;
            }
            this.drawFeature(feature, lineOptions);
        };

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

            if (stack === undefined) { /* do nothing */ } else if (stack === 1) {
                lineOptions.strokeOpacity = 0.9;
            }
            //this._featureMap[String(rshIndex)][segID] = { highlight: true, bounds: seg.geometry.bounds};
            feature = new OL.Feature.Vector(new OL.Geometry.LineString(vertices), {
                id: segID,
                rshIndex: rshIndex,
                rshID: rshID,
                stack: stack
            }, lineOptions);
            //this._featureMap[String(rshIndex)][segID] = { highlight: true, bounds: feature.attributes.geometry.bounds};
            //this._featureMap[String(rshIndex)][segID] = { highlight: true, feature: {attributes: {geometry: {bounds: feature.attributes.geometry.bounds}}}};
            this._featureMap[String(rshIndex)][String(segID)] = {
                highlight: true,
                feature: feature
            };
            return feature;
        };

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

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

        layer.destroyFeaturesBy = function(attrName, attrVal) {
            var destroyThese = this.getFeaturesByAttribute(attrName, attrVal);
            this.destroyFeatureArray(destroyThese);
        };

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

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

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

                for (f = featKeysSegID; f--;) {
                    segID = featKeysSegID[f];
                    segBounds = (layer._featureMap[String(featMapKey)][segID].feature && layer._featureMap[String(featMapKey)][segID].feature.id) ? layer._featureMap[String(featMapKey)][segID].feature.attributes.geometry.bounds : undefined;
                    //segBounds = this._featureMap[String(featMapKey)][segID].bounds;

                    if (segBounds !== undefined &&
                        newDataBounds.intersectsBounds(segBounds)) {
                        //_featureMapSaver[segID] = { highlight: true, bounds: segBounds};
                        _featureMapSaver[segID] = layer._featureMap[String(featMapKey)][segID];
                    }
                }
                console.debug(Object.keys(_featureMapSaver).length);
                layer._featureMap[String(featMapKey)] = _featureMapSaver;
            });
        };

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

        return layer;
    };

    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: false
        });

        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 = addStuffToLayerObject(rsh_ol);

        return rsh_ol;
    };

    var initUnderRoadsLayer = function() {
        var rsh_olu = new OL.Layer.Vector("Road Selector Highlights (Under)", { //W.map.getLayersByName("Road Selector Highlights")[0]
            rendererOptions: {
                zIndexing: true
            },
            uniqueName: '__RSel_Highlights_Under',
            displayInLayerSwitcher: false,
            draggable: false
        });

        I18n.translations.en.layers.name.__RSel_Highlights_Under = 'Road Selector Highlights (Under)';
        Waze.map.addLayer(rsh_olu);
        Waze.map.addControl(new OL.Control.DrawFeature(rsh_olu, OL.Handler.Path));
        rsh_olu.setZIndex(334);
        rsh_olu.setVisibility(rsh.showSessionHighlights);
        rsh_olu._featureMap = {
            "0": {}
        };
        rsh_olu = addStuffToLayerObject(rsh_olu);

        return rsh_olu;
    };

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

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

    var populateStylesMenu = function() {
        var styleNames = Object.keys(rsh.STYLES).sort(),
            numStyles = styleNames.length,
            stylesHTML = '',
            sn, styleName;

        // The following is for when populateStylesMenu() is being called for the purpose of resetting the presets
        $('.rsh-styles-menu li>a>span.fa-times').parents('li').remove();

        // Add styles to menu
        for (sn = 0; sn < numStyles; sn++) {
            styleName = styleNames[sn];
            if (!/^Default$/.test(styleName)) {
                stylesHTML += '<li><a name="' + styleName + '">' + styleName + '<span class="fa fa-times fa-pull-right fa-lg"></span></a></li>';
            }
        }
        $('.rsh-styles-menu ul').append(stylesHTML);

        // Add event listeners
        $('.rsh-styles-menu li>a').click(stylesMenuActions);
        $('.rsh-styles-menu li>a>span.fa-times').click(function(e) {
            e.stopPropagation();
            styleMenuDelete(this);
        });
    };

    var stylesMenuActions = function(e) {
        switch (this.name) {
            case 'saveas':
                e.stopPropagation();
                var inputName = prompt("Please enter a name for your style:"),
                    styleNames = Object.keys(rsh.STYLES);

                if (inputName !== null) {
                    rsh.STYLES[inputName] = rsh.HIGHLIGHTS[rsh.idx].style;

                    if (!styleNames.contains(inputName)) {
                        $('.rsh-styles-menu ul').append('<li><a name="' + inputName + '">' + inputName + '<span class="fa fa-times fa-pull-right fa-lg"></span></a></li>');
                        $('.rsh-styles-menu li>a[name="' + inputName + '"]').click(stylesMenuActions);
                        $('.rsh-styles-menu li>a[name="' + inputName + '"]>span.fa-times').click(function(e) {
                            e.stopPropagation();
                            styleMenuDelete(this);
                        });
                    }

                    localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
                }
                break;
            case 'saveasdefault':
                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;
            case 'resetpresets':
                $.extend(rsh.STYLES, rsh.presets.lineStyles);
                localStorage.RSHighlights_Styles = JSON.stringify(rsh.STYLES);
                populateStylesMenu();
                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.debug(str);
        return str;
    }

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

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

            // Load autosaved parameters from sessionStorage
            if (sessionStorage.RSHighlights) {
                rsh.importHighlights(JSON.parse(sessionStorage.RSHighlights));
                //console.debug("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 {
                rsh.addStyles();
            }

            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
                document.getElementById("cbRSHignoreEditable").checked = /&e/.test(localStorage.RSHighlights_Prefs); //ignore editable only
            }

            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; background: transparent; border-style: inset; margin-top: 0; border-width: 2px; border-color: rgba(146,194,209,.60); ' +
                '   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; color: #59899E;}\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; padding: 8px 0px 0px; margin: 0; border: 0; background-color: rgba(216, 231, 236, 0.3); }\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 { font-weight: 600; text-decoration: none; width: 130%; padding: 1px 3px;}' +
                '.rsh-btn-container>label { padding-left: 4px; }' +
                '.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-checkbox-row { margin-top: -3px; padding: 0px 10px 2px; }\n' +
                '.rsh-checkbox-row label { padding-top: 3px; margin: 0; color: #F5F5F5; font-size: 11px; }\n' +
                '.rsh-checkbox-row:hover, .rsh-checkbox-row:focus {background-color: rgba(255, 255, 255, 0.18); }\n';

            // Buttons
            rshCSS.innerHTML +=
                '.rsh-btn-color {width: 24px; height: 25px; position: absolute; top: 0px; left: 0px; margin-right: -3px; padding: 0; z-index: 0; }\n' +
                '.rsh-btn.rsh-btn-color { border-radius: 5px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; z-index: 0; padding: 2px; padding-left: 4px; border-bottom: 2px solid rgba(0,0,0,0.3); }\n' +
                '.rsh-btn.rsh-btn-highlight { border-radius: 4px; width: 75px; 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 { 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-counter:hover, .rsh-btn-counter:active, .rsh-btn-counter.active, .rsh-btn-counter:focus {opacity: 0.7;}\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: #08648E; }\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-btn>.tooltip-inner { font-weight: bold; }\n' +
                '.fa-bars.rsh-icn, .rsh-styles-menu .fa-caret-square-o-down { 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; padding-right: 10px; white-space: normal; cursor: pointer; }\n' +
                '.rsh-styles-menu .fa-times:hover, .rsh-styles-menu .fa-times:focus, .rsh-styles-menu .fa-times:active {color: #ed503b;}\n' +
                '.rsh-styles-menu .fa-times { 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 { background-color: inherit; right: -92px; padding: 1px; border-radius: 2px; font-size: 10px; max-height: 400px; width: 301px; overflow-x: hidden; overflow-y: auto;}\n' +
                '.dropdown-menu.rsh-expr-menu>li { background-color: inherit; border-bottom: 1px solid #93C4D3; margin: 0; padding: 0; }\n' +
                '.rsh-expr-item { opacity: 1; background-color: white; word-wrap: break-word; white-space: normal; padding-top: 8px; padding-bottom: 8px; padding-left: 10px; padding-right: 10px; position: relative;}\n' +
                '.dropdown-menu.rsh-expr-menu .rsh-expr-item:active, .rsh-expr-item.active, .rsh-expr-item:focus { background-color: #BEDCE5; }\n' +
                '.rsh-expr-item:hover { opacity: 1; cursor: pointer; background-color: #D4e7ed; }\n' +
                '.rsh-expr-menu.rsh-dragging .rsh-expr-item:hover { background-color: white; }\n' +
                '.rsh-expr-item>.rsh-expr-color { pointer-events: none; text-shadow: 0px 0px 1px gray; margin-left: 2px; margin-right: 6px; margin-top: 2px; position: absolute; font-size: 11px; }\n' +
                '.rsh-expr-item>.rsh-expr-text { pointer-events: auto; z-index: 1; position: relative; left: 20px; display: inline-block; width: 265px; margin-right: -10px; padding-right: 22px; margin-bottom: -8px}\n' +
                '.rsh-expr-item>.rsh-expr-dragicn { z-index: 10; font-size: 14px; color: #BBB; margin-top: -3px; font-weight: normal; width: 25px; text-align: center; position: absolute; padding-right: 5px; right: 2px;}\n' +
                '.rsh-expr-item.active>.rsh-expr-dragicn { color: #8FB3C1; }\n' +
                '.rsh-expr-item>.rsh-expr-dragicn:hover, .rsh-expr-item>.rsh-expr-dragicn:focus { cursor: move; color: #81ABBB }\n' +
                '.dropdown-menu.rsh-expr-menu .rsh-expr-item.rsh-drag { box-shadow: inset 0px -8px 0px #93C4D3 }\n' +
                '.rsh-expr-menu>li.rsh-hidden {display: none; height: 9px; }\n' +
                '.rsh-expr-menu.rsh-dragging>li.rsh-hidden { z-index: 1; display: block; height: auto;}\n' +
                '.rsh-expr-menu>li.rsh-hidden>.rsh-expr-item { width: 100%; padding: 0; }\n' +
                '.rsh-expr-menu>li.rsh-hidden>.rsh-expr-item>.rsh-expr-text { pointer-events: auto; display: block; height: 8px; margin: 0; padding: 0}\n' +
                '.rsh-expr-menu>li.rsh-hidden>.rsh-expr-item.rsh-drag { box-shadow: inset 0px -20px 0px #93C4D3; }\n' +
                '.rsh-expr-menu>li.rsh-hidden>.rsh-expr-item.rsh-drag>.rsh-expr-text { pointer-events: auto; height: 20px;}\n' +
                '.rsh-expr-item .rsh-expr-spacer { pointer-events: none; width: 100%; display: block; height: 8px;  }\n' +
                '.rsh-expr-item.rsh-drag .rsh-expr-spacer { height: 16px; }\n' +
                '.rsh-expr-menu.rsh-dragging>li:nth-child(2)>div.rsh-expr-item { margin-top: -9px;}\n' +
                '.rsh-expr-export {margin-left: 4px; cursor: pointer; color: #598991; }\n' +
                '#rshMenuNote { white-space: normal; background-color: rgba(0,0,0,0.85); border-radius: 5px; color: white; font-size: 10px; padding: 5px; font-weight: 600; font-family: "Open Sans", "Alef", helvetica, sans-serif;}\n' +
                '.rsh-expr-del { pointer-events: auto; opacity: 0; background-color: inherit; display: inline-block; position: absolute; font-size: 16px; line-height: 0.9; color: #ed503b; z-index: 1;}\n' +
                '.rsh-expr-del:hover, .rsh-expr-del:active, .rsh-expr-del:focus, .rsh-expr-item:hover .rsh-expr-del {opacity: 1;}\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>#CCFFCC</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 rsh-btn-color" style="pointer-events: none; z-index: 1; background-color: rgb(128, 0, 255);">' +
                '<span class="fa fa-caret-down fa-lg"></span>' +
                '</div>' +
                '		<button id="btnRSHighlight" class="btn btn-primary rsh-btn rsh-btn-highlight active" data-toggle="tooltip" title="Update map highlights"> Highlight</button>' + // HIGHLIGHT
                '   </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 highlights of current selection" 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 another highlighter" 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" title="View highlighting rules" data-toggle="tooltip">' + // 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 highlighter" 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" data-toggle="tooltip" title="Delete all highlighters" 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: 6px 0px; border-top: 1px solid #93C4D3; border-bottom: 1px solid #93C4D3; background-color: rgba(89, 137, 158, 0.7);">' +
                '	<div class="rsh-checkbox-row"><div class="checkbox-inline">' +
                '		<input type="checkbox" id="cbRSHSuppRoad" class="btn" style="margin-right: 5px; margin-bottom: 7px;"></input>' + // Suppress roads
                '       <label for="cbRSHSuppRoad" data-toggle="tooltip" title="Hides roads by making highlights opaque, while still allowing highlights drawn over the same segment to have an overlapping colors effect.">Suppress apperance of roads under highlights</label>' +
                '   </div></div>' +
                '	<div class="rsh-checkbox-row">' +
                '   <div class="checkbox-inline" style="width: 210px;">' +
                '       <input type="checkbox" id="cbRSHBgHighlight" class="btn">' +
                '       <label for="cbRSHBgHighlight" data-toggle="tooltip" title="Activate this option to visually block irrelevant segments that do not meet the selection criteria. You can turn off the Roads layer to give all non-highlighted segments a uniform appearance.">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="position: relative; right: -5px; top: 3px; width: 44px; height: 21px; padding: 3px 7px; background-color: rgba(255, 255, 255, 0.25); border: 1px solid transparent; " ' +
                '           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 class="rsh-checkbox-row"><div class="checkbox-inline">' +
                '       <input type="checkbox" id="cbRSHignoreEditable" class="btn" style="margin-right: 5px; margin-bottom: 7px;"></input>' + // Ignore editable only checkbox
                '       <label for="cbRSHignoreEditable" data-toggle="tooltip" title="Checking this option will make RSel Highlights ignore the "Editable only" checkbox.">Always highlight both editable and noneditable segments</label>' +
                '   </div></div>' +
                '</div>' +
                '<div class="form-inline" style="vertical-align: middle; margin: 6px -2px 0px -2px; position: relative;">' +
                '<select id="selRSHighlighter" class="form-control" style="font-weight: normal; font-size: 12px; padding: 0; width: 55%; max-width: 145px; height: 24px;"><option value="" selected>(Saved Highlighters)</option></select>' +
                '<div class="btn-group" style="position: absolute; right: 1px; top: 1px;">' +
                '<a id="applyRSHighlighter" title="Load up selected highlighter set" style="width: 23px;" class="btn btn-primary rsh-btn2"><i class="fa fa-upload fa-fw"></i></a>' +
                '<a id="saveRSHighlighter" title="Save highlighting session" class="btn btn-primary rsh-btn2"><i class="fa fa-save fa-fw"></i></a>' +
                '<a id="delRSHighlighter" title="Delete selected highlighter set" 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 set" class="btn btn-primary rsh-btn2"><i class="fa fa-reply fa-flip-horizontal fa-fw"></i></a>' +
                '<a id="exportAllRSHighlighter" title="Export all highlighter sets" 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');

            // 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-down"></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-up"></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="height: 100%; padding: 0; background: transparent; border: 0;">\
                    <span class="fa fa-caret-square-o-down" style="font-size: 11px; height: 100%; line-height: 0.5;"></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><a name="resetpresets" href="#">Reload Original Presets</a></li>\
                      <li class="divider"></li>\
                      <li class="dropdown-header" style="padding: 2px 15px; font-size: 9px; text-transform: uppercase;">Apply Style Preset</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>' +
                '<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 style="padding-left: 3px; padding-right: 3px; border-left: 1px solid #CBE1E8 !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>' + '' +
                '<select id="selRSHCap" class="rsh-dropbar-panel" style="border-color: #B4B4B4; width: 69px;"></select></td>' +
                '<td><input type="checkbox" id="cbRSHunderRoads" class="btn" style="margin: 0px 7px 7px 5px;"><label for="cbRSHunderRoads" class="fa fa-road fa-2x" data-toggle="tooltip" title="Draw highlight color under segments rather than on top" style="color: #333; background-color: gold; padding: 1px 0px 1px;"></label></td>' +
                '</tr></table>' +
                '</div></div>');


            $("div#divRSHmainPanel [data-toggle=tooltip], div#divRSHdropbarPanel [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: 600; !important"></div></div>'
            });
        };
        //---------------------------------------
        // Insert UI into side-panel of WME under RSel tab
        setupRSHInterface();
        toggleDropBarMenu(true);
        //---------------------------------------
        // Retrieve any data from saved sessionStorage
        var rsh = checkForSessionSave();
        //---------------------------------------

        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            highlighterNames = Object.keys(rsh_saved);

        for (var k of highlighterNames) {
            document.getElementById("selRSHighlighter").add(new Option(k));
        }

        // select from highlighter list if name is present
        if (rsh.highlighterName)
            document.getElementById("selRSHighlighter").value = rsh.highlighterName;

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

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

        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]));
        }

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

    // Update panel
    updatePanelFromPrefs();
    //rsh.recreateExpressionText();
    updateExprMenu(rsh.loadedSavedSession);
    if (rsh.loadedSavedSession) disableEraseButton();
    populateStylesMenu();
    rsh_seg = [];

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

    userBgHighlight = document.getElementById("cbRSHBgHighlight").checked;
    userEditableOnly = !(!document.getElementById("cbRSEditable").checked || document.getElementById('cbRSHignoreEditable').checked);
    userSuppressRoad = document.getElementById("cbRSHSuppRoad").checked;

    //------------------------------------------------------------------------
    // Setup event listeners/triggers
    var closeExprMenu = function() {
        document.getElementById('btnRSHmenu').classList.remove('open');
        window.removeEventListener('click', closeExprMenu, false);
        //$('.rsh-expr-menu').css('display','');
        //document.getElementsByClassName('rsh-expr-menu')[0].removeEventListener('mouseleave', closeExprMenu, false);
    };

    document.getElementById('btnRSHCount').addEventListener('click', function(e) {
        if (document.getElementById('btnRSHmenu').classList.toggle('open')) {
            updateExprMenu();
            setTimeout(function() {
                window.addEventListener('click', closeExprMenu, false)
            }, 100);
        } else {
            setTimeout(function() {
                window.removeEventListener('click', closeExprMenu, false);
                //$('.rsh-expr-menu').css('display','');
                //document.getElementsByClassName('rsh-expr-menu')[0].addEventListener('mouseleave', closeExprMenu, false);
            }, 100);
        }
    }, false);
/*
    document.getElementById('btnRSHCount').addEventListener('mouseenter', function() {
        updateExprMenu();
        //document.getElementById('btnRSHmenu').classList.add('open');
        document.getElementsByClassName('rsh-expr-menu')[0].style.display = 'block';
        setTimeout(function() {
            document.getElementsByClassName('rsh-expr-menu')[0].addEventListener('mouseleave', closeExprMenu, false);
            //window.addEventListener('click', closeExprMenu, false);
        }, 100);
    }, false);
*/

    //--------
    document.getElementById("selRSHColors").addEventListener('click', function() {
        document.getElementById("selRSHColors").onchange = function() {
            rsh.triggerUpdate = true;
            updatePrefsFromPanel();
            updateHighlighterColorButton();
            drawHighlights([],rsh.idx);
            document.getElementById("selRSHColors").onchange = null;
        };
    }, false);
    //-----
    document.getElementById('btnRSHighlight').onclick = drawHighlights;
    document.getElementById('btnRSHighlight').ondblclick = function() {
        requestAnimationFrame(function() {
            Highlight(true)
        })
    }; //destroy all features then highlight
    document.getElementById('btnRSHAdd').onclick = addNewHighlighter;
    document.getElementById('btnRSHSubtract').onclick = deleteHighlighter;
    document.getElementById('btnRSHClearTop').onclick = eraseHighlight;
    document.getElementById('btnRSHClearAll').onclick = clearAllHighlights;
    document.getElementById('icnRSHmoreOpts').onclick = function() {
        rsh.storeCurrentExpression();
        ($('#rshMoreOptions').css('display') === 'none') ? $('#rshMoreOptions').css('display', 'block') : $('#rshMoreOptions').css('display', 'none');
    };
    document.getElementById('aRSHdropbarLinkHide').onclick = function() {
        toggleDropBarMenu(true)
    };
    document.getElementById('aRSHdropbarLinkShow').onclick = function() {
        toggleDropBarMenu(false)
    };
    //--------------------------------------------------------------------
    document.getElementById('cbRSHSuppRoad').onclick = function() {
        userSuppressRoad = this.checked;
        if (this.checked) localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&s|$/, '&s'); //suppress
        else localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&s/, '');
        requestAnimationFrame(function() {
            Highlight(true)
        });
    };

    document.getElementById("cbRSHBgHighlight").onclick = function() {
        userBgHighlight = this.checked;
        if (userBgHighlight) localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&t|$/, '&t'); //trace
        else localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&t/, '');
        //rsh.triggerUpdate = true;
        //updatePrefsFromPanel();
        requestAnimationFrame(function() {
            Highlight(true)
        });
    };

    document.getElementById('cbRSHignoreEditable').onclick = function() {
        userEditableOnly = !(!document.getElementById("cbRSEditable").checked || this.checked);
        if (!userEditableOnly) localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&e|$/, '&e'); //ignore editable only
        else localStorage.RSHighlights_Prefs = localStorage.RSHighlights_Prefs.replace(/&e/, '');
        requestAnimationFrame(function() {
            Highlight('a')
        }); //recheck all rules and only draw if feature does not exist
    };

    document.getElementById("cbRSEditable").addEventListener('click', function() {
        userEditableOnly = !(!this.checked || document.getElementById('cbRSHignoreEditable').checked);
        requestAnimationFrame(function() {
            Highlight('a')
        }); //recheck all rules and only draw if feature does not exist
    }, false);
    //--------------------------------------------------------------------
    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('cbRSHunderRoads').onclick = function() {
        rsh.triggerUpdate = true;
        updatePrefsFromPanel();
        requestAnimationFrame(Highlight);
    };

    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 && this.value !== '') {
            $('#applyRSHighlighter').css('color', '#2196F3');
        } else {
            $('#applyRSHighlighter').css('color', '');
        }
    };

    document.getElementById('applyRSHighlighter').onclick = function() {
        $('#applyRSHighlighter').css('color', '');

        if (document.getElementById("selRSHighlighter").value !== 0) {
            var rsh_saved = JSON.parse(localStorage.RSHighlights.replace(/("#[\w\d]{6})\s?/ig, '$1')),
                highlighterName = document.getElementById("selRSHighlighter").value,
                mergeSet = confirm('Press "OK" to add the highlighters to your current session or "Cancel" to clear your highlights and then load the set.'),
                numInSet;

            if (mergeSet) {
                numInSet = rsh.HIGHLIGHTS.length;
                rsh.HIGHLIGHTS = rsh.HIGHLIGHTS.concat(rsh_saved[highlighterName].HIGHLIGHTS);
                rsh.lastIdx = rsh.HIGHLIGHTS.length - 1;
                //rsh.idx = rsh.lastIdx;
                for (var i = numInSet, iLength = rsh.lastIdx; i<=iLength; i++) {
                    rsh.rulesStack.push(i);
                }

                rsh.loadedSavedSession = true;
                rsh.triggerUpdate = true;
                rsh.HIGHLIGHTS.forEach(function(a){a.rule.tryOnce = false;});
            } else {
                //RSelExprParser.rselButtons.clear();
                rsh.importHighlights(rsh_saved[highlighterName]);
                rsh.addStyles();
            }

            rsh.highlighterName = highlighterName;
            updatePanelFromPrefs();
            drawHighlights([],'import'); // passing true forces all highlights to be destroyed

            document.getElementById('icnRSHmoreOpts').click();
            setTimeout(function(){document.getElementById('btnRSHCount').click()}, 100);
        }
    };

    document.getElementById('saveRSHighlighter').onclick = function() {
        $('#applyRSHighlighter').css('color', '');
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            selectedName = document.getElementById('selRSHighlighter').value;

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

        if (highlighterName !== null && highlighterName !== '') {
            rsh_saved[highlighterName] = {
                HIGHLIGHTS: rsh.HIGHLIGHTS
            };
            localStorage.RSHighlights = JSON.stringify(rsh_saved).replace(/("#[\w\d]{6})\s?/ig, '$1');

            var matchIndex = null,
                o,
                highlighters = document.getElementById('selRSHighlighter').options;
            for (o = highlighters.length; o--;) {
                if (new RegExp('^' + highlighterName + '$').test(highlighters[o].value)) {
                    matchIndex = o;
                    break;
                }
            }
            if (matchIndex !== null) {
                document.getElementById("selRSHighlighter").options[matchIndex].remove();
            }

            addHighlighterName.appendChild(document.createTextNode(highlighterName));
            document.getElementById("selRSHighlighter").appendChild(addHighlighterName);
            document.getElementById("selRSHighlighter").selectedIndex = document.getElementById("selRSHighlighter").options.length - 1;
            $('#applyRSHighlighter').css('color', '');
        }
    };

    document.getElementById('delRSHighlighter').onclick = function() {
        $('#applyRSHighlighter').css('color', '');
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            highlighterName = document.getElementById("selRSHighlighter").value,
            selIdx = document.getElementById("selRSHighlighter").selectedIndex,
            userConfirm;

        if (selIdx) { //prevent the first option from being deleted
            userConfirm = confirm('Are you sure that you want to delete "' + highlighterName + '"?');
            if (userConfirm) {
                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 text string:'),
            rsh_import, rsh_saved, msgStr, importedSomething = false,
            selectedName = document.getElementById('selRSHighlighter').value,
            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.replace(/("#[\w\d]{6})\s?/ig, '$1')); //remove space after color HEX, in case it was copied from slack
            if (rsh_import.constructor === Object) {
                for (var r in rsh_import) { //do a quick check
                    if (!rsh_import[r].HIGHLIGHTS) {
                        importedSomething = false;
                        break;
                    } else {
                        importedSomething = true;
                    }
                }

                if (importedSomething) {
                    // Check for pre-existing names
                    var matchIndex = [],
                        o, i,
                        importNames = Object.keys(rsh_import),
                        highlighters = document.getElementById('selRSHighlighter').options;

                    for (o = highlighters.length; o--;) {
                        for (i = importNames.length; i--;) {
                            if (new RegExp('^' + importNames[i] + '$').test(highlighters[o].value)) {
                                matchIndex.push(o);
                            }
                        }
                    }
                    //remove duplicate names
                    if (matchIndex.length) {
                        for (var m = 0, mLength = matchIndex.length; m < mLength; m++) {
                            document.getElementById("selRSHighlighter").options[matchIndex[m]].remove();
                        }
                    }
                    //add names for all imported highlighters
                    for (var n of importNames) {
                        document.getElementById("selRSHighlighter").add(new Option(n));
                    }

                    //get saved data from localStorage
                    rsh_saved = JSON.parse(localStorage.RSHighlights);

                    //merge saved data
                    $.extend(true, rsh_saved, rsh_import);
                    console.debug(rsh_saved);

                    // save to localStorage
                    localStorage.RSHighlights = JSON.stringify(rsh_saved);

                    //put up import note
                    var numImported = importNames.length;
                    msgStr = 'Imported ' + numImported + ' set(s) of highlighters';
                    setTimeout(function() {
                        $('#importRSHighlighter').append(htmlStr + msgStr + '</div>')
                    }, 300);
                    setTimeout(function() {
                        $('#rshMenuNote').remove()
                    }, 5000);

                    $('#applyRSHighlighter').css('color', '#2196F3');
                }
            } 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()
                }, 5000);
            }
            document.getElementById('rshMenuNote').onclick = function(){this.remove()};
        } //else prompt was cancelled
    };

    document.getElementById('exportRSHighlighter').onclick = function(){
        $('#applyRSHighlighter').css('color', '');
        exportRSelHighlighter();
    };

    document.getElementById('exportAllRSHighlighter').onclick = function() {
        $('#applyRSHighlighter').css('color', '');
        var rsh_saved = JSON.parse(localStorage.RSHighlights),
            htmlStr = '<div id="rshMenuNote" style="position: absolute;bottom: 110%;width: 100px;left: -52px;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).replace(/("#[\w\d]{6})\s?/ig, '$1'));

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

    var timeoutCanceller;
    // 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('d'); //destroy all features, but not featureMaps
        timeoutCanceller1 = setTimeout(function(){
            Highlight(); //continue and draw any segs that were missing in first round
            timeoutCanceller2 = setTimeout(function(){
                Highlight(null); //continue and now also add segments that are off-screen
            }, 1000);
        }, 1000)
    });*/

    Waze.map.events.register("moveend", Waze.map, function(evt) {
        clearTimeout(timeoutCanceller);
        //console.debug('WMERSH','********* Timeout Cancelled **********');
        setTimeout(function() {
            //console.debug('WMERSH','Highlight() - 1');
            Highlight(); //assumes no changes in rules, highlight styles, etc
            timeoutCanceller = setTimeout(function() {
                //console.debug('WMERSH','Highlight() - 2');
                Highlight();
                timeoutCanceller = setTimeout(function() {
                    //console.debug('WMERSH','Highlight() - 3');
                    Highlight();
                    /*timeoutCanceller = setTimeout(function() {
                        Highlight(null)
                    }, 800);*/
                }, 800);
            }, 800);
        }, 100);
    });

    Waze.model.actionManager.events.register("afterundoaction", null, function(evt) {
        //console.debug(evt);
        evt.object.actions.map(function(a) {
            if (a.changedSegStates) {
                a.changedSegStates.map(function(segID) {
                    if (segID && segID.constructor === Number) rsh_OL.destroyFeaturesBy('id', segID);
                });
                Highlight();
            } else if (a.segment && a.segment.attributes && a.segment.attributes.id) {
                rsh_OL.destroyFeaturesBy('id', a.segment.attributes.id);
                Highlight();
            }
        });
    });


    Waze.model.actionManager.events.register("afterclearactions", null, function(evt) {
        Highlight(true);
    });


    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);
    //------------------------------------------------------------------------

    zoomLevel = W.map.zoom;
    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;
                    if (typeof $ === 'undefined') {
                        $ = unsafeWindow.$;
                        W = Waze = unsafeWindow.Waze;
                    }

                    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);