/*
Copyright 2012-2021 VeloViewer. All Rights Reserved
###V33###
 */
L.FunctionButtons = L.Control.extend({
    includes: L.Mixin.Events, //L.Evented

    initialize: function(buttons, options) {
        if (!('push' in buttons && 'splice' in buttons))
            buttons = [buttons];
        this._buttons = buttons;
        if (!options && buttons.length > 0 && 'position' in buttons[0])
            options = {
                position: buttons[0].position
            };
        L.Control.prototype.initialize.call(this, options);
    },

    onAdd: function(map) {
        this._map = map;
        this._links = [];

        var container = L.DomUtil.create('div', 'leaflet-bar');
        for (var i = 0; i < this._buttons.length; i++) {
            var button = this._buttons[i],
                link = L.DomUtil.create('a', '', container);
            link._buttonIndex = i; // todo: remove?
            link.href = button.href || '#';
            if (button.href)
                link.target = 'funcbtn';
            link.style.padding = '0 4px';
            link.style.width = 'auto';
            link.style.minWidth = '20px';
            if (button.bgColor)
                link.style.backgroundColor = button.bgColor;
            if (button.title)
                link.title = button.title;
            button.link = link;
            this._updateContent(i);

            var stop = L.DomEvent.stopPropagation;
            L.DomEvent
                .on(link, 'click', stop)
                .on(link, 'mousedown', stop)
                .on(link, 'dblclick', stop)
                .on(link, 'mouseover', function() {
                    map.dragging.disable();
                    map.scrollWheelZoom.disable();
                })
                .on(link, 'mouseout', function() {
                    map.dragging.enable();
                    map.scrollWheelZoom.enable();
                });
            if (!button.href)
                L.DomEvent
                .on(link, 'click', L.DomEvent.preventDefault)
                .on(link, 'click', this.clicked, this);
        }

        return container;
    },

    _updateContent: function(n) {
        if (n >= this._buttons.length)
            return;
        var button = this._buttons[n],
            link = button.link,
            content = button.content;
        if (!link)
            return;
        if (content === undefined || content === false || content === null || content === '')
            link.innerHTML = button.alt || '&nbsp;';
        else if (typeof content === 'string') {
            var ext = content.length < 4 ? '' : content.substring(content.length - 4),
                isData = content.substring(0, 11) === 'data:image/';
            if (ext === '.png' || ext === '.gif' || ext === '.jpg' || isData) {
                link.style.width = '' + (button.imageSize || 26) + 'px';
                link.style.height = '' + (button.imageSize || 26) + 'px';
                link.style.padding = '0';
                link.style.backgroundImage = 'url(' + content + ')';
                link.style.backgroundRepeat = 'no-repeat';
                link.style.backgroundPosition = button.bgPos ? (-button.bgPos[0]) + 'px ' + (-button.bgPos[1]) + 'px' : '0px 0px';
            } else
                link.innerHTML = content;
        } else {
            while (link.firstChild)
                link.removeChild(link.firstChild);
            link.appendChild(content);
        }
    },

    setContent: function(n, content) {
        if (content === undefined) {
            content = n;
            n = 0;
        }
        if (n < this._buttons.length) {
            this._buttons[n].content = content;
            this._updateContent(n);
        }
    },

    setBgColor: function(n, colour) {
        if (n < this._buttons.length) {
            this._buttons[n].link.style.backgroundColor = colour;
            //this._updateContent(n);
        }
    },

    getBgColor: function(n) {
        if (n < this._buttons.length) {
            return this._buttons[n].link.style.backgroundColor;
            //this._updateContent(n);
        }
    },

    setTitle: function(n, title) {
        if (title === undefined) {
            title = n;
            n = 0;
        }
        if (n < this._buttons.length) {
            var button = this._buttons[n];
            button.title = title;
            if (button.link)
                button.link.title = title;
        }
    },

    setBgPos: function(n, bgPos) {
        if (bgPos === undefined) {
            bgPos = n;
            n = 0;
        }
        if (n < this._buttons.length) {
            var button = this._buttons[n];
            button.bgPos = bgPos;
            if (button.link)
                button.link.style.backgroundPosition = bgPos ? (-bgPos[0]) + 'px ' + (-bgPos[1]) + 'px' : '0px 0px';
        }
    },

    setHref: function(n, href) {
        if (href === undefined) {
            href = n;
            n = 0;
        }
        if (n < this._buttons.length) {
            var button = this._buttons[n];
            button.href = href;
            if (button.link)
                button.link.href = href;
        }
    },

    clicked: function(e) {
        var link = (window.event && window.event.srcElement) || e.target || e.srcElement;
        while (link && 'tagName' in link && link.tagName !== 'A' && !('_buttonIndex' in link))
            link = link.parentNode;
        if ('_buttonIndex' in link) {
            var button = this._buttons[link._buttonIndex];
            if (button) {
                if ('callback' in button)
                    button.callback.call(button.context);
                this.fire('clicked', {
                    idx: link._buttonIndex
                });
            }
        }
    }
});

L.functionButtons = function(buttons, options) {
    return new L.FunctionButtons(buttons, options);
};

var mapProxy = https + '://',
    lm_optionButtons, lm_CurrentPositionButton, watchPositionMarker = null,
    watchPosition = null,
    watchPositionTimer = null,
    lm_tileLines, lm_showtiles = false,
    lm_mapLineSmoothing = 0,
    lm_mapLine = {
        color: '#FF032E',
        weight: 3,
        opacity: 0.5,
        smoothFactor: 1
    },
    lm_mapLineHighlight = {
        color: '#3C90A8',
        opacity: 1,
        weight: 3
    },
    lm_mapLineBGDef = {
        opacity: 1,
        weight: 5,
        pointRadius: 0.5,
        color: '#000',
        smoothFactor: lm_mapLineSmoothing
    }, photoLayer, photoData = [],
    lm_mapLineStyles = {},
    lm_tiles = {},
    lm_squares = {},
    lm_clusters = {},
    lm_currentBaseLayer,
    mapInitiated = {},
    lm_initialLoad = true,
    retinaImg = L.Browser.retina ? '@2x' : '',
    z_london, z_watopia, z_newyork, z_richmond, z_innsbruck, z_yorkshire, z_critcity, z_france, z_italy, z_champs, z_overlay, zwiftMaps,
    lm_ZwiftMapsMarker = typeof(getCookie('lm_ZwiftMapsMarker')) !== 'undefined' && +getCookie('lm_ZwiftMapsMarker') == 1;

// please create and use your own thunderforest key - https://www.thunderforest.com/
var lm_thunderforestKey = '3e8acaf16b3f4e36a80a1732e9e82c40',
    lm_tfa = 'Maps © <a href="http://www.thunderforest.com">Thunderforest</a>, Data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
    lm_tfOptions = {
        maxZoom: 18,
        attribution: lm_tfa
    },
    lm_layerVeloviewer = new L.TileLayer('https://{s}.tile.thunderforest.com/veloviewer-oh4/{z}/{x}/{y}' + retinaImg + '.png?apikey=' + lm_thunderforestKey, lm_tfOptions);
lm_layerVeloviewer.id = 'veloviewer';


var lm_layerCustom, lm_layerLive, lm_baseLayers;

// please create and use your own Open Spaces key - https://www.ordnancesurvey.co.uk/
if (typeof(embed) === 'undefined' || !embed) {
    warpedOS = new WarpedOsOpenSpaceMapType('CFA53F206D869053E0405F0ACA604903', 'https://veloviewer.com');

    var lm_layerLandscape = new L.TileLayer('https://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}' + retinaImg + '.png?apikey=' + lm_thunderforestKey, lm_tfOptions);
    lm_layerLandscape.id = 'landscape';

    var lm_layerCycle = new L.TileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}' + retinaImg + '.png?apikey=' + lm_thunderforestKey, lm_tfOptions);
    lm_layerCycle.id = 'ocm';
    var lm_layerOutdoors = new L.TileLayer('https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}' + retinaImg + '.png?apikey=' + lm_thunderforestKey, lm_tfOptions);
    lm_layerOutdoors.id = 'outdoors';
    var lm_layerOSM = new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
        maxZoom: 18
    });
    lm_layerOSM.id = 'osm';
    // valid values are 'roadmap', 'satellite', 'terrain' and 'hybrid'
    var lm_layerGTerrain = L.tileLayer('http://{s}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}', {
        maxZoom: 20,
        subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
    });
    lm_layerGTerrain.id = 'terrain';
    var lm_layerGHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
        maxZoom: 20,
        subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
    });
    lm_layerGHybrid.id = 'hybrid';
    var lm_layerGSatellite = L.tileLayer('http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
        maxZoom: 20,
        subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
    });
    lm_layerGSatellite.id = 'satellite';
    var lm_layerBaW = new L.TileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}' + retinaImg + '.png', {
        maxZoom: 18,
        attribution: '<a id="home-link" target="_top" href="../">Map tiles</a> by <a target="_top" href="http://stamen.com">Stamen Design</a>, under <a target="_top" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'
    });
    lm_layerBaW.id = 'baw';

    L.GridLayer.OS = L.GridLayer.extend({
        createTile: function(coords) {
            return warpedOS.getTile({
                x: coords.x,
                y: coords.y
            }, coords.z, document);
        }
    });
    L.gridLayer.debugCoords = function(opts) {
        return new L.GridLayer.OS(opts);
    };

    var lm_layerOS = L.gridLayer.debugCoords({
        attribution: WarpedOsOpenSpaceMapType.prototype.copyright
    });
    lm_layerOS.id = 'osuk';

    lm_baseLayers = {
        //"Live": {},
        "Custom": {},
        "VeloViewer": lm_layerVeloviewer,
        "Landscape": lm_layerLandscape,
        "Outdoors": lm_layerOutdoors,
        "Open Cycle": lm_layerCycle,
        "Open Street": lm_layerOSM,
        "Black & White": lm_layerBaW,
        "OS (UK only)": lm_layerOS,
        "Terrain": lm_layerGTerrain,
        "Hybrid": lm_layerGHybrid,
        "Satellite": lm_layerGSatellite
    };

    if (typeof(mbCustomToken) !== 'undefined' && mbCustomToken != '') {
        if (mbCustomToken.substr(0, 5) == 'https') {
            lm_layerCustom = new L.TileLayer(mbCustomToken /*.replace(/\{/g, '${')*/ .replace('{y}', '{y}' + retinaImg), {
                attribution: '© MapBox, © OpenStreetMap'
            });
        } else {
            lm_layerCustom = new L.TileLayer("https://{s}.tiles.mapbox.com/v4/" + mbCustomDomain + "/{z}/{x}/{y}" + retinaImg + ".png?access_token=" + mbCustomToken, {
                attribution: '© MapBox, © OpenStreetMap'
            });
        }
        lm_layerCustom.id = 'custom';
        lm_baseLayers.Custom = lm_layerCustom;
    } else {
        delete lm_baseLayers.Custom;
    }

    /*if (typeof(mbLiveToken) !== 'undefined' && mbLiveToken != '') {
        lm_layerLive = new L.TileLayer('https://api.mapbox.com/styles/v1/' + mbLiveDomain + '/tiles/256/{z}/{x}/{y}' + retinaImg + '?access_token=' + mbLiveToken, {
            attribution: '© MapBox, © OpenStreetMap'
        });
        lm_layerLive.id = 'live';
        lm_baseLayers.Live = lm_layerLive;
    } else {
        delete lm_baseLayers.Live;
    }*/
} else {
    lm_baseLayers = {
        "Landscape": lm_layerLandscape
    }
}

var lm_distanceMarkers = [];

function showDistanceMarker(ll, text, lmap) {
    if (!lm_distanceMarkers[ll.join('-') + '-' + text]) {
        var marker = new L.circleMarker(ll, {
            fillColor: '#000',
            fillOpacity: 1,
            radius: 2,
            stroke: false
        });
        marker.bindTooltip(text + ' ' + lrgLenUnit, {
            permanent: true,
            className: "leafletDistanceMarker"
        });
        //lm_distanceMarkers[ll.join('-') + '-' + text] = marker;
    }
    marker.addTo(lmap);
    //lm_distanceMarkers[ll.join('-') + '-' + text].addTo(map);
    marker.shownOnMap = true;
    return marker;
}

function hideDistanceMarker(marker, lmap) {
    lmap.removeLayer(marker);
    marker.shownOnMap = false;
}

function setLMapDistMarkers(lmap, distA, llA) {
    if (getParameterByName('hideDistMarker') == '1') return;
    if (lmap.getBounds() != null) {
        if (lmap.distanceMarkers && lmap.distanceMarkers.length) {
            lmap.distanceMarkers.forEach(function(d) {
                lmap.removeLayer(d);
            });
        }
        lmap.distanceMarkers = [];
        var lDistanceMarkers = [];
        if ((typeof(embed) === 'undefined' && embed) || getParameterByName('p') != '' || (document.getElementById('map' + lmap.suffix + 'DistanceMarkerCheckBox') != null && !document.getElementById('map' + lmap.suffix + 'DistanceMarkerCheckBox').checked)) return;
        var dist = 1;
        var z = lmap.getZoom();
        if (z >= 17) dist = 0.1;
        if (z == 16) dist = 0.25;
        if (z == 15) dist = 0.5;
        if (z == 14) dist = 1;
        if (z == 13) dist = 2;
        if (z == 12) dist = 5;
        if (z == 11) dist = 10;
        if (z <= 10) dist = 25;

        var lMapB = lmap.getBounds();

        lDistanceMarkers = d3.range(distA[0], distA.last() + 1, dist / lrgLenMult).map(function(d, i) {
            return {
                d: d,
                t: i * dist,
                i: d3.bisectLeft(distA, d)
            }
        }).filter(function(d) {
            return d.i < distA.length && d.d != 0;
        }).map(function(d) {
            var ll = llA[d.i]
            if (distA[d.i] != d.d) {
                var dd = distA[d.i] - distA[d.i - 1];
                var de = d.d - distA[d.i - 1];
                ll = {
                    lat: llA[d.i - 1].lat + (de / dd) * (llA[d.i].lat - llA[d.i - 1].lat),
                    lng: llA[d.i - 1].lng + (de / dd) * (llA[d.i].lng - llA[d.i - 1].lng)
                };
            }
            return {
                i: d.i,
                t: (Math.round(100 * d.t) / 100).toString(),
                ll: ll
            };
        }).filter(function(d) {
            return (typeof(filtIndExtent) === 'undefined' || (d.i >= filtIndExtent[0] && d.i <= filtIndExtent[1])) &&
                d.ll.lat >= lMapB._southWest.lat && d.ll.lat <= lMapB._northEast.lat && d.ll.lng >= lMapB._southWest.lng && d.ll.lng <= lMapB._northEast.lng
        });

        lmap.distanceMarkers = lDistanceMarkers.map(function(d) {
            return showDistanceMarker([d.ll.lat, d.ll.lng], d.t, lmap);
        });
    }
}

function showExplorerTilesLoaded(obj, result) {
    tilesCalculated = true;
    routeExplorerTiles = uncompressTiles(result.t, 14);
    var sum;
    routeExplorerTiles = setExplorer(sum, routeExplorerTiles, true);
    drawTiles(routeExplorerTiles);
    if (lm_initialLoad) {
        if (+getCookie('ExplorerClusterShown')) {
            lm_showClusterBounds();
        } else {
            if (+getCookie('ExplorerMaxSquareShown')) {
                lm_showMaxSquareBounds();
            }
        }
        lm_initialLoad = false;
    }
    lm_showHideTileLines();
    if (obj.func) {
        obj.func();
    }
}

function showExplorerTiles(f) {
    if (typeof(lm_optionButtons) === 'undefined') return;
    if (typeof(map) !== 'undefined' && map != null) {
      var msi = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxSqaure');
      var mci = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxCluster');
        if (lm_optionButtons.getBgColor(msi) == '' && lm_optionButtons.getBgColor(mci) == '') {
            for (var att in lm_tiles) {
                map.removeLayer(lm_tiles[att]);
                delete lm_tiles[att];
            }
        }
        if (lm_optionButtons.getBgColor(msi) == '') {
            for (var att in lm_squares) {
                map.removeLayer(lm_squares[att]);
                delete lm_squares[att];
            }
        }
        if (lm_optionButtons.getBgColor(mci) == '') {
            for (var att in lm_clusters) {
                map.removeLayer(lm_clusters[att]);
                delete lm_clusters[att];
            }
        }
        if (lm_optionButtons.getBgColor(msi) != '' || lm_optionButtons.getBgColor(mci) != '') {
            if (cpref == 'r') {
                if (!tilesCalculated) {
                    if (explorerTilesSrc.match(/\.[0-9]*\.js/) == null) {
                        alert('Explorer data not yet generated. Please visit your Summary page.');
                        return;
                    }
                    $.ajax({
                        url: explorerTilesSrc,
                        func: f,
                        async: true,
                        tryCount: 0,
                        success: function(result) {
                            showExplorerTilesLoaded(this, result);
                        },
                        error: function(result) {
                            this.tryCount++;
                            if (this.tryCount == 1) {
                                $.ajax({
                                    parentCall: this,
                                    async: true,
                                    url: '/api/checkName.php?n=' + this.url.substr(this.url.indexOf('athletes2')),
                                    success: function(d) {
                                        if (d.status == 'success') {
                                            var n = d.name.replace(/^.*\.([0-9]*)\.js$/, '$1')
                                            this.parentCall.url = this.parentCall.url.replace(/\.[0-9]*\./, '.' + n + '.');
                                            $.ajax(this.parentCall);
                                        } else {
                                            alert('Sorry, there has been a problem geting your explored tiles (1).');
                                        }
                                    }
                                });
                                return false;
                            } else {
                                alert('Sorry, there has been a problem geting your explored tiles (2).');
                            }
                        }
                    });
                    return;
                }
                drawTiles(routeExplorerTiles);
            } else {
                if (!tilesCalculated) {
                    var fAct = a.filter(function(d) {
                        return typeof(d.map) !== 'undefined' && d.map != null
                    });
                    fAct.forEach(function(d) {
                        getActMapTiles(d, explorerZoom, 100);
                    });
                    tilesCalculated = true;
                }
                var sum;
                var tiles = setExplorer(sum, liveData.filter(function(d) {
                    return typeof(d.map) !== 'undefined' && d.map != null
                }), true);
                drawTiles(tiles);
            }
        }
        //}
        if (lm_initialLoad) {
            if (+getCookie('ExplorerClusterShown')) {
                lm_showClusterBounds();
            } else {
                if (+getCookie('ExplorerMaxSquareShown')) {
                    lm_showMaxSquareBounds();
                }
            }
            lm_initialLoad = false;
        }
        lm_showHideTileLines();
        if (f) f();
    }
}

function showExplorerColour() {
    d3.select('#explorerConfigModalInfo').html(explorerConfigInfoText);
}

function updateExplorerColour(a, noSave) {
    var ah = Math.round(Math.round(a._a * 255)).toString(16);
    var r = Math.round(a._r).toString(16);
    var g = Math.round(a._g).toString(16);
    var b = Math.round(a._b).toString(16);
    var h = '#' + (r.length == 1 ? '0' : '') + r + (g.length == 1 ? '0' : '') + g + (b.length == 1 ? '0' : '') + b;
    var h1 = '#' + (ah.length == 1 ? '0' : '') + ah + (r.length == 1 ? '0' : '') + r + (g.length == 1 ? '0' : '') + g + (b.length == 1 ? '0' : '') + b;
    switch (this.id) {
        case 'explorerTileStroke':
            explorerTileStyle.color = h;
            explorerTileStyle.opacity = a._a;
            explorerClumpStyle.color = h;
            explorerClumpStyle.opacity = a._a;
            break;
        case 'explorerTileFill':
            explorerTileStyle.fillColor = h;
            explorerTileStyle.fillOpacity = a._a;
            break;
        case 'explorerMaxSquareStroke':
            maxSquareStyle.color = h;
            maxSquareStyle.opacity = a._a;
            break;
        case 'explorerMaxClumpFill':
            explorerClumpStyle.fillColor = h;
            explorerClumpStyle.fillOpacity = a._a;
            break;
        case 'explorerTileStrokeSat':
            explorerTileStyleSat.color = h;
            explorerTileStyleSat.opacity = a._a;
            explorerClumpStyleSat.color = h;
            explorerClumpStyleSat.opacity = a._a;
            break;
        case 'explorerTileFillSat':
            explorerTileStyleSat.fillColor = h;
            explorerTileStyleSat.fillOpacity = a._a;
            break;
        case 'explorerMaxSquareStrokeSat':
            maxSquareStyleSat.color = h;
            maxSquareStyleSat.opacity = a._a;
            break;
        case 'explorerMaxClumpFillSat':
            explorerClumpStyleSat.fillColor = h;
            explorerClumpStyleSat.fillOpacity = a._a;
            break;
    }
    explorerConfig[this.id] = h1;
    if (typeof(noSave) === 'undefined') {
        d3.select('#explorerConfigModalInfo').html('Saving...');
        $.ajax({
            url: '/api/saveExplorerConfig.php',
            type: "POST",
            data: {
                d: JSON.stringify(explorerConfig)
            },
            async: true,
            success: function(result) {
                if (result.status == 'success') {
                    d3.select('#explorerConfigModalInfo').html('Changes saved.');
                } else {
                    d3.select('#explorerConfigModalInfo').html('Did not save!');
                }
                setTimeout(showExplorerColour, 5000);
            },
            error: function(result) {
                d3.select('#explorerConfigModalInfo').html('Did not save!');
                setTimeout(showExplorerColour, 5000);
            }
        });
    }
    lm_updateLayerStyles()
}
if (explorerConfig == '') {
    explorerConfig = {};
} else {
    explorerConfig = JSON.parse(explorerConfig);
    if (explorerConfig.explorerTileStroke) {
        explorerTileStyle.color = '#' + explorerConfig.explorerTileStroke.substr(3);
        explorerTileStyle.opacity = parseInt(explorerConfig.explorerTileStroke.substr(1, 2), 16) / 255;
        explorerClumpStyle.color = '#' + explorerConfig.explorerTileStroke.substr(3);
        explorerClumpStyle.opacity = parseInt(explorerConfig.explorerTileStroke.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerTileFill) {
        explorerTileStyle.fillColor = '#' + explorerConfig.explorerTileFill.substr(3);
        explorerTileStyle.fillOpacity = parseInt(explorerConfig.explorerTileFill.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerMaxSquareStroke) {
        maxSquareStyle.color = '#' + explorerConfig.explorerMaxSquareStroke.substr(3);
        maxSquareStyle.opacity = parseInt(explorerConfig.explorerMaxSquareStroke.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerMaxClumpFill) {
        explorerClumpStyle.fillColor = '#' + explorerConfig.explorerMaxClumpFill.substr(3);
        explorerClumpStyle.fillOpacity = parseInt(explorerConfig.explorerMaxClumpFill.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerTileStrokeSat) {
        explorerTileStyleSat.color = '#' + explorerConfig.explorerTileStrokeSat.substr(3);
        explorerTileStyleSat.opacity = parseInt(explorerConfig.explorerTileStrokeSat.substr(1, 2), 16) / 255;
        explorerClumpStyleSat.color = '#' + explorerConfig.explorerTileStrokeSat.substr(3);
        explorerClumpStyleSat.opacity = parseInt(explorerConfig.explorerTileStrokeSat.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerTileFillSat) {
        explorerTileStyleSat.fillColor = '#' + explorerConfig.explorerTileFillSat.substr(3);
        explorerTileStyleSat.fillOpacity = parseInt(explorerConfig.explorerTileFillSat.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerMaxSquareStrokeSat) {
        maxSquareStyleSat.color = '#' + explorerConfig.explorerMaxSquareStrokeSat.substr(3);
        maxSquareStyleSat.opacity = parseInt(explorerConfig.explorerMaxSquareStrokeSat.substr(1, 2), 16) / 255;
    }
    if (explorerConfig.explorerMaxClumpFillSat) {
        explorerClumpStyleSat.fillColor = '#' + explorerConfig.explorerMaxClumpFillSat.substr(3);
        explorerClumpStyleSat.fillOpacity = parseInt(explorerConfig.explorerMaxClumpFillSat.substr(1, 2), 16) / 255;
    }
}
var explorerConfigInfoText = '<button style="float:right" class="btn btn-primary btn-mini" onclick="resetExplorerConfig()">Reset</button>Customise your Explorer tile colours (also apply to <a href="http://blog.veloviewer.com/veloviewer-chrome-extension-for-strava-website/" target="_blank">Google Chrome extension</a>).';
var explorerConfigData = [{
    id: 'explorerTileStroke',
    desc: 'Tile border',
    default: '#7FFF0000'
}, {
    id: 'explorerTileFill',
    desc: 'Visited fill',
    default: '#19FF0000'
}, {
    id: 'explorerMaxSquareStroke',
    desc: 'Max Square border',
    default: '#7F0000FF'
}, {
    id: 'explorerMaxClumpFill',
    desc: 'Max Cluster fill',
    default: '#360000FF'
}, {
    id: 'explorerNewTileFill',
    desc: 'New tile fill',
    default: '#70eaff00'
}, {
    id: 'explorerTileStrokeSat',
    desc: 'Tile border',
    default: '#7FFFFFFF'
}, {
    id: 'explorerTileFillSat',
    desc: 'Visited fill',
    default: '#19FFFFFF'
}, {
    id: 'explorerMaxSquareStrokeSat',
    desc: 'Max Square border',
    default: '#7FFFFFFF'
}, {
    id: 'explorerMaxClumpFillSat',
    desc: 'Max Cluster fill',
    default: '#4CFFFFFF'
}, {
    id: 'explorerNewTileFillSat',
    desc: 'New tile fill',
    default: '#4CFFFFFF'
}];

function resetExplorerConfig() {
    explorerConfigData.forEach(function(d) {
        $('#' + d.id).spectrum('set', d.default);
        updateExplorerColour.bind(d3.select('#' + d.id).node(), $('#' + d.id).spectrum('get'))();
    });
    d3.select('#explorerConfigModalInfo').html('Saving...');
    $.ajax({
        url: '/api/saveExplorerConfig.php',
        type: "POST",
        data: {
            d: JSON.stringify(explorerConfig)
        },
        async: true,
        success: function(result) {
            if (result.status == 'success') {
                d3.select('#explorerConfigModalInfo').html('Changes saved.');
            } else {
                d3.select('#explorerConfigModalInfo').html('Did not save!');
            }
            setTimeout(showExplorerColour, 5000);
        },
        error: function(result) {
            d3.select('#explorerConfigModalInfo').html('Did not save!');
            setTimeout(showExplorerColour, 5000);
        }
    });
    lm_updateLayerStyles();
}

function lm_updateLayerStyles() {
    for (var att in lm_tiles) {
        lm_tiles[att].setStyle(lm_currentBaseLayer.id == 'hybrid' ? explorerTileStyleSat : explorerTileStyle)
    }
    for (var att in lm_squares) {
        lm_squares[att].setStyle(lm_currentBaseLayer.id == 'hybrid' ? maxSquareStyleSat : maxSquareStyle)
    }

    var tileClumpStyle = eval('explorerClumpStyle' + (lm_currentBaseLayer.id == 'hybrid' ? sat : ''));
    tileClumpStyle.clickable = false;
    var tileMinorClumpStyle = clone(tileClumpStyle);
    tileMinorClumpStyle.fillOpacity = tileMinorClumpStyle.fillOpacity / 2;

    for (var att in lm_clusters) {
        if (lm_clusters[att].maxClump) {
            lm_clusters[att].setStyle(tileClumpStyle)
        } else {
            lm_clusters[att].setStyle(tileMinorClumpStyle);
        }
    }
    lm_tileLines.setStyle(lm_currentBaseLayer.id == 'hybrid' ? explorerTileStyleSat : explorerTileStyle);
}

function lm_showMaxSquareBounds() {
    var br = explorerTiles[(explorerMaxs[0].x + explorerMaxs[0].size - 1) + '-' + (explorerMaxs[0].y + explorerMaxs[0].size - 1)];
    if (typeof(br) === 'undefined') {
        br = explorerMaxs[0];
    }
    br = br.br;
    var tl = explorerMaxs[0].tl;
    map.fitBounds([
        [tl.y, tl.x],
        [br.y, br.x]
    ]);
}

function lm_showClusterBounds() {
    var ca = d3.keys(maxClump).map(function(d) {
        return d.split('-').map(function(e, i) {
            return +e
        })
    });
    if (ca.length == 0) return;
    var cXExt = d3.extent(ca, function(d) {
        return d[0]
    });
    var cYExt = d3.extent(ca, function(d) {
        return d[1]
    });
    var tl = {
        x: tile2long(cXExt[0], 14),
        y: tile2lat(cYExt[0], 14)
    };
    var br = {
        x: tile2long(cXExt[1] + 1, 14),
        y: tile2lat(cYExt[1] + 1, 14)
    };
    //var expCExt = new OpenLayers.Bounds(tl.x - (br.x - tl.x) * 0.25, tl.y - (br.y - tl.y) * 0.25, br.x + (br.x - tl.x) * 0.25, br.y + (br.y - tl.y) * 0.25).transform(fromProjection, toProjection);
    map.fitBounds([
        [tl.y, tl.x],
        [br.y, br.x]
    ]);
}

function lm_showHideTileLines() {
    if (typeof(lm_optionButtons) === 'undefined') return;
    var msi = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxSqaure');
    var mci = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxCluster');
    if (localMap.getZoom() < 9 || !lm_showtiles || (lm_optionButtons.getBgColor(msi) == '' && lm_optionButtons.getBgColor(mci) == '')) {
        if (lm_tileLines && localMap.hasLayer(lm_tileLines)) {
            localMap.removeLayer(lm_tileLines);
        }
    }
    if (localMap.getZoom() >= 9 && lm_showtiles && (lm_optionButtons.getBgColor(msi) != '' || lm_optionButtons.getBgColor(mci) != '')) {
        if (lm_tileLines && !localMap.hasLayer(lm_tileLines)) {
            window.setTimeout(function() {
                localMap.addLayer(lm_tileLines);
                lm_tileLines.setStyle(lm_currentBaseLayer.id == 'hybrid' ? explorerTileStyleSat : explorerTileStyle);
            }, 250);
        }
    }
}

function setUnexploredKML() {
    if (typeof(lm_optionButtons) === 'undefined') return;
    if (d3.select('#exportUnexploredKml').node() == null) return;

    d3.select('#exportUnexploredKml')
        .attr('href', '#')
        .attr('download', null)
        .classed('disabled', true);

    var msi = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxSqaure');
    var mci = lm_optionButtons._buttons.map(function(d){return d.type}).indexOf('maxCluster');
    if ((lm_optionButtons.getBgColor(msi) == '' && lm_optionButtons.getBgColor(mci) == '')) {
        d3.select('#exportUnexploredKmlText')
            .style('display', null)
            .html('Please turn on max square/cluster.');
        return;
    }

    if (map.getZoom() >= 8) {
        var kml = '<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"><Document><name>Unexplored Tiles - Veloviewer</name><open>1</open><Style id="s"><LineStyle><color>' + explorerTileStyle.color.replace(/#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/, 'ff$3$2$1') + '</color><width>1</width></LineStyle></Style><Folder><name>Unexplored Tiles - Veloviewer</name><open>1</open>';

        var tc = 0;
        for (var x = long2tile(map.getBounds().getWest(), 14); x <= long2tile(map.getBounds().getEast(), 14); x++) {
            for (var y = lat2tile(map.getBounds().getNorth(), 14); y <= lat2tile(map.getBounds().getSouth(), 14); y++) {
                if (!explorerTiles[x + '-' + y]) {
                    kml += '<Placemark><styleUrl>#s</styleUrl><LineString><coordinates>' + (+tile2long(x, 14) + (+tile2long(x + 1, 14) - tile2long(x, 14)) / 10).toFixed(6) + ',' + (+tile2lat(y, 14) + (+tile2lat(y + 1, 14) - tile2lat(y, 14)) / 10).toFixed(6) + ' ' + tile2long(x, 14).toFixed(6) + ',' + tile2lat(y, 14).toFixed(6) + ' ' + tile2long(x + 1, 14).toFixed(6) + ',' + tile2lat(y, 14).toFixed(6) + ' ' + tile2long(x + 1, 14).toFixed(6) + ',' + tile2lat(y + 1, 14).toFixed(6) + ' ' + tile2long(x, 14).toFixed(6) + ',' + tile2lat(y + 1, 14).toFixed(6) + ' ' + tile2long(x, 14).toFixed(6) + ',' + tile2lat(y, 14).toFixed(6) + '</coordinates></LineString></Placemark>';
                    tc++;
                }
            }
        }
        kml += '</Folder></Document></kml>';

        var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

        window.URL = window.URL || window.webkitURL;

        var egtbu;
        if (iOS) {
            ekmlbu = "data:application/vnd.google-earth.kml+xml;charset=utf-8," + encodeURIComponent(kml);
        } else {
            var blob = new Blob([kml], { type: "application/vnd.google-earth.kml+xml;charset=utf-8" });
            ekmlbu = window.URL.createObjectURL(blob);
        }
        d3.select('#exportUnexploredKml')
            .attr('href', ekmlbu)
            .attr('download', 'unexplored_tiles.kml')
            .classed('disabled', false);

        d3.select('#exportUnexploredKmlText')
            .style('display', null)
            .html(fInt(tc) + ' unexplored tiles in current map view.');
    } else {
        d3.select('#exportUnexploredKmlText')
            .style('display', null)
            .html('Please zoom in.');
    }

    // also create explorered tile kml
    var kml = '<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"><Document><name>Unexplored Tiles - Veloviewer</name><open>1</open><Style id="s"><LineStyle><color>' + explorerTileStyle.color.replace(/#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/, 'ff$3$2$1') + '</color><width>1</width></LineStyle></Style><Folder><name>Unexplored Tiles - Veloviewer</name><open>1</open>';

    var tc = 0;
    d3.values(explorerTiles).forEach(function(d) {
        kml += '<Placemark><styleUrl>#s</styleUrl><LineString><coordinates>' + (+tile2long(d.x, 14) + (+tile2long(d.x + 1, 14) - tile2long(d.x, 14)) / 10).toFixed(6) + ',' + (+tile2lat(d.y, 14) + (+tile2lat(d.y + 1, 14) - tile2lat(d.y, 14)) / 10).toFixed(6) + ' ' + tile2long(d.x, 14).toFixed(6) + ',' + tile2lat(d.y, 14).toFixed(6) + ' ' + tile2long(d.x + 1, 14).toFixed(6) + ',' + tile2lat(d.y, 14).toFixed(6) + ' ' + tile2long(d.x + 1, 14).toFixed(6) + ',' + tile2lat(d.y + 1, 14).toFixed(6) + ' ' + tile2long(d.x, 14).toFixed(6) + ',' + tile2lat(d.y + 1, 14).toFixed(6) + ' ' + tile2long(d.x, 14).toFixed(6) + ',' + tile2lat(d.y, 14).toFixed(6) + '</coordinates></LineString></Placemark>';
        tc++;
    });
    kml += '</Folder></Document></kml>';

    var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

    window.URL = window.URL || window.webkitURL;

    var egtbu;
    if (iOS) {
        ekmlbu = "data:application/vnd.google-earth.kml+xml;charset=utf-8," + encodeURIComponent(kml);
    } else {
        var blob = new Blob([kml], { type: "application/vnd.google-earth.kml+xml;charset=utf-8" });
        ekmlbu = window.URL.createObjectURL(blob);
    }
    d3.select('#exportExploredKml')
        .attr('href', ekmlbu)
        .attr('download', 'explored_tiles.kml')
        .classed('disabled', false);

    d3.select('#exportExploredKmlText')
        .style('display', null)
        .html(fInt(tc) + ' ticked map tiles.');
}

function initMap(suffix, useCanvas) {
    if (typeof(cpref) !== 'undefined') lm_mapLine.opacity = typeof(getCookie(cpref + 'o')) === 'undefined' ? 0.5 : getCookie(cpref + 'o');

    if (typeof(suffix) === 'undefined' || suffix == null) suffix = '';
    if (mapInitiated['map' + suffix] == 1) return;

    mapInitiated['map' + suffix] = 1;

    d3.selectAll('#mapDomObj' + suffix).style('border', 'solid 1px #bbb').html('');

    var options = {
        zoomControl: false,
        fullscreenControl: true,
        fullscreenControlOptions: {
            position: 'topright'
        }
    };

    if (typeof(embed) === 'undefined' || !embed) {
        switch (getCookie('lmap')) {
            /*case "live":
                if (lm_baseLayers.Live) {
                    options.layers = [lm_layerLive];
                }*/
            case "custom":
                if (!options.layers && lm_baseLayers.Custom) {
                    options.layers = [lm_layerCustom];
                }
            case "landscape":
                if (!options.layers) {
                    options.layers = [lm_layerLandscape];
                }
                break;
            case "veloviewer":
                options.layers = [lm_layerVeloviewer];
                break;
            case "ocm":
                options.layers = [lm_layerCycle];
                break;
            case "outdoors":
                options.layers = [lm_layerOutdoors];
                break;
            case "osm":
                options.layers = [lm_layerOSM];
                break;
            case "terrain":
                options.layers = [lm_layerGTerrain];
                break;
            case "hybrid":
                options.layers = [lm_layerGHybrid];
                break;
            case "satellite":
                options.layers = [lm_layerGSatellite];
                break;
            case "osuk":
                options.layers = [lm_layerOS];
                break;
            case "baw":
                options.layers = [lm_layerBaW];
                break;
            default:
                /*if (lm_baseLayers.Live) {
                    options.layers = [lm_layerLive];
                }*/
                if (!options.layers && lm_baseLayers.Custom) {
                    options.layers = [lm_layerCustom];
                }
                if (!options.layers) {
                    options.layers = [lm_layerVeloviewer];
                }
        }
    } else {
        options.layers = [lm_layerVeloviewer];
    }

    lm_currentBaseLayer = options.layers[0];

    if (useCanvas) {
        options.renderer = L.canvas();
        //options.preferCanvas = true;
    }

    eval('map' + suffix + ' = new L.Map("mapDomObj' + suffix + '", options)');
    localMap = eval('map' + suffix);

    L.control.scale({
        metric: lrgLenUnit == 'km',
        imperial: lrgLenUnit != 'km',
        position: 'bottomleft'
    }).addTo(localMap);

    if (L.photo) {
      photoLayer = L.photo.cluster().on('click', function(evt) {
        var d = evt.layer.photo;
        var mw, mh = Math.min(localMap._container.getBoundingClientRect().height-150, 380);
        if (mh * d.x/d.y > 380 || mh * d.x/d.y > localMap._container.getBoundingClientRect().width-50) {
          mw = Math.min(localMap._container.getBoundingClientRect().width-50, 380);
          mh = mw * d.y/d.x;
        } else {
          mw = mh * d.x/d.y;
        }
        var template = '<a target="_blank" href="{u2048}""><img src="{url}" width="'+mw+'" height="'+mh+'" style="max-width: 100%;height: auto;"/></a><p style="max-width:'+mw+'px">{caption}</p>';

        evt.layer.bindPopup(L.Util.template(template, d), {
          className: 'leaflet-popup-photo',
          minWidth: mw
        }).openPopup();
      });
    }

    new L.Control.Zoom({ position: 'bottomright' }).addTo(localMap);

    localMap.suffix = suffix;

    zwiftMaps = L.layerGroup();
    z_london = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_London.jpg', [
        [51.5362, -0.1776],
        [51.4601, -0.0555]
    ]).addTo(zwiftMaps);
    z_london.v_north = 51.5362;
    z_london.v_south = 51.4601;
    z_london.v_east = -0.0555;
    z_london.v_west = -0.1776;
    z_london.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_London.jpg';
    z_watopia = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Watopia_V2.jpg', [
        [-11.62597, 166.87747],
        [-11.70255, 167.03255]
    ]).addTo(zwiftMaps);
    z_watopia.v_north = -11.62597;
    z_watopia.v_south = -11.70255;
    z_watopia.v_east = 167.03255;
    z_watopia.v_west = 166.87747;
    z_watopia.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Watopia_V2.jpg';
    z_newyork = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_NewYork.jpg', [
        [40.81725, -74.0227],
        [40.74085, -73.9222]
    ]).addTo(zwiftMaps);
    z_newyork.v_north = 40.81725;
    z_newyork.v_south = 40.74085;
    z_newyork.v_east = -73.9222;
    z_newyork.v_west = -74.0227;
    z_newyork.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_NewYork.jpg';
    z_richmond = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Richmond.jpg', [
        [37.5774, -77.48954],
        [37.5014, -77.394]
    ]).addTo(zwiftMaps);
    z_richmond.v_north = 37.5774;
    z_richmond.v_south = 37.5014;
    z_richmond.v_east = -77.394;
    z_richmond.v_west = -77.48954;
    z_richmond.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Richmond.jpg';
    z_innsbruck = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Innsbruck.jpg', [
        [47.2947, 11.3501],
        [47.2055, 11.4822]
    ]).addTo(zwiftMaps);
    z_innsbruck.v_north = 47.2947;
    z_innsbruck.v_south = 47.2055;
    z_innsbruck.v_east = 11.4822;
    z_innsbruck.v_west = 11.3501;
    z_innsbruck.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Innsbruck.jpg';
    z_yorkshire = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Yorkshire.jpg', [
        [54.0254, -1.6320],
        [53.9491, -1.5022]
    ]).addTo(zwiftMaps);
    z_yorkshire.v_north = 54.0254;
    z_yorkshire.v_south = 53.9491;
    z_yorkshire.v_east = -1.5022;
    z_yorkshire.v_west = -1.6320;
    z_yorkshire.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Yorkshire.jpg';
    z_critcity = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_CritCity.jpg', [
        [-10.3657, 165.7824],
        [-10.4038, 165.8207]
    ]).addTo(zwiftMaps);
    z_critcity.v_north = z_critcity.getBounds().getNorth();
    z_critcity.v_south = z_critcity.getBounds().getSouth();
    z_critcity.v_east = z_critcity.getBounds().getEast();
    z_critcity.v_west = z_critcity.getBounds().getWest();
    z_critcity.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_CritCity.jpg';

    z_france = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_France.jpg', [
      [-21.64155, 166.1384],
      [-21.7564, 166.26125]
    ]).addTo(zwiftMaps);
    z_france.v_url = z_france._url;
    z_france.v_north = z_france.getBounds().getNorth();
    z_france.v_south = z_france.getBounds().getSouth();
    z_france.v_east = z_france.getBounds().getEast();
    z_france.v_west = z_france.getBounds().getWest();

    z_italy = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Italy.jpg', [
        [44.5308037, 11.36991729102076],
        [44.45463821, 11.26261748]
    ]).addTo(zwiftMaps);
    z_italy.v_url = z_italy._url;
    z_italy.v_north = 44.45463821;
    z_italy.v_south = 44.5308037;
    z_italy.v_east = 11.26261748;
    z_italy.v_west = 11.36991729102076;
    z_italy.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Italy.jpg';

    z_champs = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_Champs.jpg', [
        [48.9058, 2.2561],
        [48.82945, 2.3722]
    ]).addTo(zwiftMaps);
    z_champs.v_url = z_champs._url;
    z_champs.v_north = z_champs.getBounds().getNorth();
    z_champs.v_south = z_champs.getBounds().getSouth();
    z_champs.v_east = z_champs.getBounds().getEast();
    z_champs.v_west = z_champs.getBounds().getWest();

    /*z_champs.v_url = 'https://cf.veloviewer.com/Zwift/MiniMap_Champs.jpg';
    z_champs.v_north = -10.3657;
    z_champs.v_south = -10.4038;
    z_champs.v_east = 165.8207;
    z_champs.v_west = 165.7824;
    z_champs = L.imageOverlay('https://cf.veloviewer.com/Zwift/MiniMap_CritCity.jpg', [
        [z_champs.v_north, z_champs.v_west],
        [z_champs.v_south, z_champs.v_east]
    ]).addTo(zwiftMaps);
*/


    z_overlay = {
        'Zwift maps': zwiftMaps
    };

    if (typeof(embed) === 'undefined' || !embed) {
        var layerControl = L.control.layers(lm_baseLayers /*, z_overlay*/ ).addTo(localMap);
        layerControl.setPosition('topleft');

        if (lm_ZwiftMapsMarker) {
            zwiftMaps.addTo(localMap);
        }

        /*localMap.on('overlayadd', function(eo) {
            setCookie('lm_ZwiftMapsMarker', 1, 365);
        });
        localMap.on('overlayremove', function(eo) {
            setCookie('lm_ZwiftMapsMarker', 0, 365);
        });*/
        //d3.selectAll('.leaflet-control-layers-list').style('margin-bottom', '0px');


        localMap.on('moveend', function() {
            if (typeof(filterActiveColumns) !== 'undefined') {
                var filterId = jsonIndexOf(filterActiveColumns, 'field', 'mapFilter');
                if ((filterId > -1) &&
                    (filterActiveColumns[filterId].filterActive == 1)) {
                    var ex = localMap.getBounds();

                    filterActiveColumns[filterId].minLat = ex._southWest.lat;
                    filterActiveColumns[filterId].maxLat = ex._northEast.lat;
                    filterActiveColumns[filterId].minLng = ex._southWest.lng;
                    filterActiveColumns[filterId].maxLng = ex._northEast.lng;

                    setMapFilterText(filterActiveColumns[filterId]);

                    clearTimeout(refreshTimer);

                    refreshTimer = setTimeout(function() {
                        dataLoaded()
                    }, Math.min(updateTime * 1.1, 250));
                }
            }
        });

        localMap.on('popupopen', function(e) {
          var id = -1;
            if (e.popup._source.vv_type && e.popup._source.vv_type == 'mapLine') {
              id =  e.popup._source.vv_id;
            }
            if (e.popup._source.photo) {
              id =  e.popup._source.photo.act.i;
            }
            if (id > -1) {
                e.popup._source.isPopupOpen = true;
                var loffset = Math.floor(liveData.map(function(d) {
                    return d.i
                }).indexOf(id) / rowCount);
                if (loffset != offset) {
                    offset = loffset;
                    dataLoaded(true);
                }
                d3.select('#tr' + id).classed('info', true);
                if (photosShownChecked) {
                  drawPhotosScroll(id);
                  d3.selectAll('.photo' + id)
                      .style('background', '#FF032E');
                }
            }
        });
        localMap.on('popupclose', function(e) {
            if (e.popup._source.vv_type && e.popup._source.vv_type == 'mapLine') {
                e.popup._source.isPopupOpen = false;
                e.popup._source.setStyle(lm_mapLine);
                d3.select('#tr' + e.popup._source.vv_id).classed('info', false);
                if (photosShownChecked) {
                  d3.selectAll('.photo' + e.popup._source.vv_id)
                      .style('background', null);
                }
            }
            if (e.popup._source.photo) {
                e.popup._source.isPopupOpen = false;
                d3.select('#tr' + e.popup._source.photo.act.i).classed('info', false);
                if (photosShownChecked) {
                  d3.selectAll('.photo' + e.popup._source.photo.act.i)
                      .style('background', null);
                }
            }
        });

        if (typeof(cpref) !== 'undefined' && suffix != 2) {
          var lButtons = [];

            if (cpref == 'a' || cpref == 'r') {
              if (cpref == 'a') {
                lButtons.push({
                    content: '<i class="icon-camera"></i>',
                    title: 'View photos',
                    bgColor: +getCookie('PhotosShown') || typeof(getCookie('PhotosShown')) === 'undefined' ? '#bbb' : null,
                    type: 'photos'
                });
              }
              lButtons.push({
                  content: '&#9634',
                  title: 'View Explorer Max Square',
                  bgColor: +getCookie('ExplorerMaxSquareShown') ? '#bbb' : null,
                  type: 'maxSqaure'
              });
                lButtons.push({
                    title: 'View Explorer Max Cluster',
                    bgColor: +getCookie('ExplorerClusterShown') ? '#bbb' : null,
                    content: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="margin-left: -3px;margin-top: -2px;"><style type="text/css">.fg {fill:#333;stroke-width: 0px;}.bg {fill:white;stroke-width: 0px;}</style><g><rect class="fg" x="15" y="9" width="1" height="1"/><rect class="fg" x="14" y="9" width="1" height="1"/><rect class="fg" x="13" y="9" width="1" height="1"/><rect class="fg" x="14" y="8" width="1" height="1"/><rect class="fg" x="14" y="10" width="1" height="1"/><rect class="fg" x="12" y="9" width="1" height="1"/><rect class="fg" x="13" y="8" width="1" height="1"/><rect class="fg" x="13" y="10" width="1" height="1"/><rect class="fg" x="14" y="7" width="1" height="1"/><rect class="fg" x="11" y="9" width="1" height="1"/><rect class="fg" x="12" y="8" width="1" height="1"/><rect class="fg" x="12" y="10" width="1" height="1"/><rect class="fg" x="13" y="7" width="1" height="1"/><rect class="fg" x="14" y="6" width="1" height="1"/><rect class="fg" x="10" y="9" width="1" height="1"/><rect class="fg" x="11" y="8" width="1" height="1"/><rect class="fg" x="11" y="10" width="1" height="1"/><rect class="fg" x="12" y="7" width="1" height="1"/><rect class="fg" x="13" y="6" width="1" height="1"/><rect class="fg" x="14" y="5" width="1" height="1"/><rect class="fg" x="9" y="9" width="1" height="1"/><rect class="fg" x="10" y="8" width="1" height="1"/><rect class="fg" x="10" y="10" width="1" height="1"/><rect class="fg" x="11" y="7" width="1" height="1"/><rect class="fg" x="12" y="6" width="1" height="1"/><rect class="fg" x="13" y="5" width="1" height="1"/><rect class="fg" x="14" y="4" width="1" height="1"/><rect class="fg" x="8" y="9" width="1" height="1"/><rect class="fg" x="9" y="8" width="1" height="1"/><rect class="fg" x="9" y="10" width="1" height="1"/><rect class="fg" x="10" y="7" width="1" height="1"/><rect class="fg" x="10" y="11" width="1" height="1"/><rect class="fg" x="11" y="6" width="1" height="1"/><rect class="fg" x="12" y="5" width="1" height="1"/><rect class="fg" x="13" y="4" width="1" height="1"/><rect class="fg" x="14" y="3" width="1" height="1"/><rect class="fg" x="7" y="9" width="1" height="1"/><rect class="fg" x="8" y="8" width="1" height="1"/><rect class="fg" x="8" y="10" width="1" height="1"/><rect class="fg" x="9" y="7" width="1" height="1"/><rect class="fg" x="9" y="11" width="1" height="1"/><rect class="fg" x="10" y="6" width="1" height="1"/><rect class="fg" x="11" y="5" width="1" height="1"/><rect class="fg" x="12" y="4" width="1" height="1"/><rect class="fg" x="13" y="3" width="1" height="1"/><rect class="fg" x="15" y="3" width="1" height="1"/><rect class="fg" x="6" y="9" width="1" height="1"/><rect class="fg" x="7" y="8" width="1" height="1"/><rect class="fg" x="7" y="10" width="1" height="1"/><rect class="fg" x="8" y="7" width="1" height="1"/><rect class="fg" x="8" y="11" width="1" height="1"/><rect class="fg" x="9" y="6" width="1" height="1"/><rect class="fg" x="9" y="12" width="1" height="1"/><rect class="fg" x="10" y="5" width="1" height="1"/><rect class="fg" x="11" y="4" width="1" height="1"/><rect class="fg" x="5" y="9" width="1" height="1"/><rect class="fg" x="6" y="8" width="1" height="1"/><rect class="fg" x="6" y="10" width="1" height="1"/><rect class="fg" x="7" y="7" width="1" height="1"/><rect class="fg" x="7" y="11" width="1" height="1"/><rect class="fg" x="8" y="6" width="1" height="1"/><rect class="fg" x="8" y="12" width="1" height="1"/><rect class="fg" x="9" y="5" width="1" height="1"/><rect class="fg" x="9" y="13" width="1" height="1"/><rect class="fg" x="4" y="9" width="1" height="1"/><rect class="fg" x="5" y="8" width="1" height="1"/><rect class="fg" x="5" y="10" width="1" height="1"/><rect class="fg" x="6" y="7" width="1" height="1"/><rect class="fg" x="6" y="11" width="1" height="1"/><rect class="fg" x="7" y="6" width="1" height="1"/><rect class="fg" x="7" y="12" width="1" height="1"/><rect class="fg" x="8" y="5" width="1" height="1"/><rect class="fg" x="8" y="13" width="1" height="1"/><rect class="fg" x="9" y="4" width="1" height="1"/><rect class="fg" x="10" y="13" width="1" height="1"/><rect class="fg" x="9" y="14" width="1" height="1"/><rect class="fg" x="3" y="9" width="1" height="1"/><rect class="fg" x="4" y="8" width="1" height="1"/><rect class="fg" x="4" y="10" width="1" height="1"/><rect class="fg" x="5" y="7" width="1" height="1"/><rect class="fg" x="5" y="11" width="1" height="1"/><rect class="fg" x="6" y="12" width="1" height="1"/><rect class="fg" x="7" y="13" width="1" height="1"/><rect class="fg" x="8" y="4" width="1" height="1"/><rect class="fg" x="8" y="14" width="1" height="1"/><rect class="fg" x="10" y="14" width="1" height="1"/><rect class="fg" x="9" y="15" width="1" height="1"/><rect class="fg" x="2" y="9" width="1" height="1"/><rect class="fg" x="3" y="8" width="1" height="1"/><rect class="fg" x="3" y="10" width="1" height="1"/><rect class="fg" x="4" y="7" width="1" height="1"/><rect class="fg" x="4" y="11" width="1" height="1"/><rect class="fg" x="5" y="12" width="1" height="1"/><rect class="fg" x="6" y="13" width="1" height="1"/><rect class="fg" x="7" y="14" width="1" height="1"/><rect class="fg" x="7" y="4" width="1" height="1"/><rect class="fg" x="8" y="15" width="1" height="1"/><rect class="fg" x="11" y="14" width="1" height="1"/><rect class="fg" x="10" y="15" width="1" height="1"/><rect class="fg" x="2" y="10" width="1" height="1"/><rect class="fg" x="3" y="7" width="1" height="1"/><rect class="fg" x="3" y="11" width="1" height="1"/><rect class="fg" x="4" y="6" width="1" height="1"/><rect class="fg" x="2" y="11" width="1" height="1"/><rect class="fg" x="3" y="12" width="1" height="1"/></g></svg>',
                    type: 'maxCluster'
                });
                lButtons.push({
                    content: '<div style="padding: 5px 0px 0px 2px;width:16px" id="showZwiftMaps">' + zwiftIconSVG + '</div>',
                    bgColor: lm_ZwiftMapsMarker ? '#bbb' : null,
                    title: 'Show Zwift maps',
                    type: 'zwift'
                });
                lButtons.push({
                    content: '<i class="icon-cog"></i>',
                    title: 'Map settings',
                    type: 'settings'
                });

                lm_CurrentPositionButton = L.functionButtons([{
                    content: '<i class="icon-screenshot"></i>',
                    title: 'Show your location'
                }], {
                    position: 'bottomright'
                });

                lm_CurrentPositionButton.on('clicked', function(data) {
                    if (lm_CurrentPositionButton.getBgColor(data.idx) == '') {
                        lm_CurrentPositionButton.setBgColor(data.idx, '#bbb');
                        watchPosition = navigator.geolocation.watchPosition(function(pos) {
                            var firstTime = true;
                            if (watchPositionMarker != null) {
                                firstTime = false;
                                localMap.removeLayer(watchPositionMarker);
                            }
                            watchPositionMarker = new L.Circle([pos.coords.latitude, pos.coords.longitude], {
                                radius: Math.max(10, pos.coords.accuracy)
                            });
                            watchPositionMarker.addTo(localMap);
                            if (firstTime) {
                                localMap.setZoom(15);
                                if (watchPositionTimer != null) {
                                    window.clearTimeout(watchPositionTimer);
                                }
                                watchPositionTimer = window.setTimeout(function() {
                                    watchPositionTimer = null;
                                }, 5200);
                            }
                            if (watchPositionTimer != null) {
                                localMap.panTo(new L.LatLng(pos.coords.latitude, pos.coords.longitude));
                            }
                        }, function(err) {
                            lm_CurrentPositionButton.setBgColor(data.idx, null);
                            console.warn('ERROR(' + err.code + '): ' + err.message);
                        }, {
                            enableHighAccuracy: true,
                            timeout: 1000,
                            maximumAge: 0
                        });
                    } else {
                        lm_CurrentPositionButton.setBgColor(data.idx, null);
                        if (watchPosition != null) {
                            navigator.geolocation.clearWatch(watchPosition);
                            watchPosition = null;
                        }
                        if (watchPositionMarker != null) {
                            localMap.removeLayer(watchPositionMarker);
                            watchPositionMarker = null;
                        }
                    }
                });
                localMap.addControl(lm_CurrentPositionButton);
            } else {
                lButtons = [{
                    content: '<div style="padding: 5px 0px 0px 2px;width:16px" id="showZwiftMaps">' + zwiftIconSVG + '</div>',
                    bgColor: lm_ZwiftMapsMarker ? '#bbb' : null,
                    title: 'Show Zwift maps',
                    type: 'zwift'
                }, {
                    content: '<i class="icon-cog"></i>',
                    title: 'Map settings',
                    type: 'settings'
                }];
            }

            lm_optionButtons = L.functionButtons(lButtons, {
                position: 'topright'
            });

            lm_optionButtons.on('clicked', function(data) {
                if (cpref == 'a' || cpref == 'r') {
                    switch (lButtons[data.idx].type) {
                      case 'photos':
                          if (lm_optionButtons.getBgColor(data.idx) == '') {
                              lm_optionButtons.setBgColor(data.idx, '#bbb');
                              photoLayer.addTo(localMap);
                              setCookie('PhotosShown', 1, 355);
                          } else {
                              lm_optionButtons.setBgColor(data.idx, null);
                              localMap.removeLayer(photoLayer);
                              setCookie('PhotosShown', 0, 355);
                          }
                          break;
                          case 'maxSqaure':
                              if (lm_optionButtons.getBgColor(data.idx) == '') {
                                  lm_optionButtons.setBgColor(data.idx, '#bbb');
                                  showExplorerTiles(lm_showMaxSquareBounds);
                                  setCookie('ExplorerMaxSquareShown', 1, 355);
                              } else {
                                  lm_optionButtons.setBgColor(data.idx, null);
                                  setCookie('ExplorerMaxSquareShown', 0, 355);
                                  showExplorerTiles();
                              }
                              break;
                        case 'maxCluster':
                            if (lm_optionButtons.getBgColor(data.idx) == '') {
                                lm_optionButtons.setBgColor(data.idx, '#bbb');
                                showExplorerTiles(lm_showClusterBounds);
                                setCookie('ExplorerClusterShown', 1, 355);
                            } else {
                                lm_optionButtons.setBgColor(data.idx, null);
                                setCookie('ExplorerClusterShown', 0, 355);
                                showExplorerTiles();
                            }
                            break;
                        case 'zwift':
                            if (lm_ZwiftMapsMarker) {
                                lm_optionButtons.setBgColor(data.idx, null);
                                localMap.removeLayer(zwiftMaps);
                                lm_ZwiftMapsMarker = 0;
                            } else {
                                lm_optionButtons.setBgColor(data.idx, '#bbb');
                                zwiftMaps.addTo(localMap);
                                lm_ZwiftMapsMarker = 1;
                            }
                            setCookie('lm_ZwiftMapsMarker', lm_ZwiftMapsMarker ? '1' : '0', 365);
                            break;
                        case 'settings':
                            $('#map' + localMap.suffix + 'ConfigModal').modal({
                                backdrop: false
                            });
                            setUnexploredKML();
                            break;
                    }
                } else {
                    switch (data.idx) {
                        case 0:
                            if (lm_ZwiftMapsMarker) {
                                lm_optionButtons.setBgColor(data.idx, null);
                                localMap.removeLayer(zwiftMaps);
                                lm_ZwiftMapsMarker = 0;
                            } else {
                                lm_optionButtons.setBgColor(data.idx, '#bbb');
                                zwiftMaps.addTo(localMap);
                                lm_ZwiftMapsMarker = 1;
                            }
                            setCookie('lm_ZwiftMapsMarker', lm_ZwiftMapsMarker ? '1' : '0', 365);
                            break;
                        case 1:
                            $('#map' + localMap.suffix + 'ConfigModal').modal({
                                backdrop: false
                            });
                            break;
                    }
                }
            });
            localMap.addControl(lm_optionButtons);

            addModalPopup('map' + suffix + 'Config', 'Map Settings', undefined, 0, d3.select(localMap._container));

            d3.select('#map' + suffix + 'ConfigModal').on('mouseover', function() {
                localMap.dragging.disable();
                localMap.scrollWheelZoom.disable();
            })
            d3.select('#map' + suffix + 'ConfigModal').on('mouseout', function() {
                localMap.dragging.enable();
                localMap.scrollWheelZoom.enable();
            })

            var f = d3.select('#map' + suffix + 'ConfigModal .modal-body').append('div').classed('form-horizontal', true);

            if (cpref == 'sd') {
                cg = f.append('div').classed('control-group', true).style('margin-bottom', (cpref || cpref == 'r' == 'a' ? '' : '1') + '0px').attr('title', 'Show distance markers.');
                cg.append('label').classed('control-label', true).text('Distance markers:');
                cg.append('div').classed('controls', true).append('input')
                    .attr({
                        'id': 'map' + suffix + 'DistanceMarkerCheckBox',
                        'class': 'map' + suffix + 'DistanceMarkerCheckBox',
                        'type': 'checkbox',
                        'checked': typeof(getCookie('lm_distanceMarker')) === 'undefined' || +getCookie('lm_distanceMarker') == 1 ? 'checked' : null
                    })
                    .on('change', function() {
                        setCookie('lm_distanceMarker', this.checked ? 1 : 0, 365);
                        if (typeof(lm_onzoomend) !== 'undefined') lm_onzoomend();
                    });

            } else {
                var cg = f.append('div').classed('control-group', true).style('margin-bottom', '0px').attr('title', 'The opacity/transparency of your activitiy trails. 0 for transparent, 1 for opaque or anyone in between.');
                cg.append('label').classed('control-label', true).text('Line opacity:');
                cg.append('div').classed('controls', true).append('input')
                    .attr({
                        'class': 'lm_opacitySlider',
                        'type': 'range',
                        'min': 0,
                        'max': 1,
                        'step': 0.1,
                        'value': lm_mapLine.opacity
                    })
                    .on('change', function() {
                        lm_mapLine.opacity = +this.value;
                        setCookie(cpref + 'o', +this.value, 355);
                        a.forEach(function(d) {
                            if (d.mapLineStyle) {
                                d.mapLineStyle.opacity = +lm_mapLine.opacity;
                                if (d.ll) d.ll.setStyle(d.mapLineStyle);
                            } else {
                                if (d.ll) d.ll.setStyle(lm_mapLine);
                            }
                        });
                    });

                /*cg = f.append('div').classed('control-group', true).style('margin-bottom', '0px').attr('title', 'The colour for the lines on the map.');
                cg.append('label').classed('control-label', true).text('Line colour:');
                d3.select(this).append('label').classed('control-label', true).attr('for', 'lineColour').html(d.desc + ':');
                d3.select(this).append('div').classed('controls', true).each(function() {
                    d3.select(this).append('input').attr('type', 'text').attr('id', 'lineColourPicker');
                });
                $('#lineColourPicker').spectrum({
                    showAlpha: true,
                    change: updateLineColour,
                    //move: updateExplorerColour,
                    allowEmpty: false,
                    showButtons: false,
                    preferredFormat: 'hex',
                    show: showLineColour,
                    appendTo: d3.select('#map' + suffix + 'ConfigModal .modal-body').node()
                });
                $('#' + d.id).spectrum('set', explorerConfig[d.id] ? explorerConfig[d.id] : d.default);*/

                cg = f.append('div').classed('control-group', true).style('margin-bottom', '0px').attr('title', 'Filter your list of activities to just those completely enclosed by the boundaries of the map.');
                cg.append('label').classed('control-label', true).text('Filter to map:');
                cg.append('div').classed('controls', true).append('input')
                    .attr({
                        'id': 'mapFilterCheckBox',
                        'class': 'mapFilterCheckBox',
                        'type': 'checkbox'
                    })
                    .on('change', function() {
                        var filterId = jsonIndexOf(columns, 'field', 'mapFilter');
                        if (filterId == -1) {
                            columns.push({
                                title: 'Map',
                                desc: 'Map filter',
                                field: 'mapFilter',
                                minLat: 0,
                                maxLat: 0,
                                minLng: 0,
                                maxLng: 0,
                                type: 'map',
                                display: 'map',
                                filterActive: 0,
                                hideZeros: 0,
                                displayOrder: -1
                            });
                        }
                        filterId = jsonIndexOf(columns, 'field', 'mapFilter');

                        if (this.checked) {
                            var ex = localMap.getBounds();

                            columns[filterId].minLat = ex._southWest.lat;
                            columns[filterId].maxLat = ex._northEast.lat;
                            columns[filterId].minLng = ex._southWest.lng;
                            columns[filterId].maxLng = ex._northEast.lng;
                            columns[filterId].filterActive = 1;

                            setMapFilterText(columns[filterId]);
                        } else {
                            columns[filterId].filterActive = 0;
                        }

                        offset = 0;
                        setFilterActive();
                        applyFilters(a);
                        dataLoaded();
                        updateActiveFilters();
                        updateAvailableFilters();
                    });

                cg = f.append('div').classed('control-group', true).style('margin-bottom', (cpref || cpref == 'r' == 'a' ? '' : '1') + '0px').attr('title', 'Auto-zoom the map to area of just the filtered activities.');
                cg.append('label').classed('control-label', true).text('Auto-zoom:');
                cg.append('div').classed('controls', true).append('input')
                    .attr({
                        'id': 'mapAutoZoomCheckBox',
                        'class': 'mapAutoZoomCheckBox',
                        'type': 'checkbox',
                        'checked': typeof(getCookie('lm_autozoom')) === 'undefined' || +getCookie('lm_autozoom') == 1 ? 'checked' : null
                    })
                    .on('change', function() {
                        setCookie('lm_autozoom', this.checked ? 1 : 0, 365);
                        setExtent(liveData);
                    });
            }
            cg = f.append('div').classed('control-group', true).style('margin-bottom', (cpref || cpref == 'r' == 'a' ? '' : '1') + '0px').attr('title', 'Show Zwift maps.');

            if (cpref == 'a' || cpref == 'r') {
                cg = f.append('div').classed('control-group', true).style('margin-bottom', '10px').attr('title', 'Show all Explorer tile lines when max square or cluster enabled and zoomed in to a certain level (9).');
                cg.append('label').classed('control-label', true).text('Explorer tile lines:');
                cg.append('div').classed('controls', true).append('input')
                    .attr({
                        'id': 'mapShowTilesCheckBox',
                        'class': 'mapShowTilesCheckBox',
                        'type': 'checkbox',
                        'checked': typeof(getCookie('lm_showtiles')) !== 'undefined' && +getCookie('lm_showtiles') == 1 ? 'checked' : null
                    })
                    .on('change', function() {
                        setCookie('lm_showtiles', this.checked ? 1 : 0, 365);
                        lm_showtiles = this.checked;
                        lm_showHideTileLines();
                    });

                lm_showtiles = typeof(getCookie('lm_showtiles')) !== 'undefined' && +getCookie('lm_showtiles') == 1;

                cg = f.append('div').classed('control-group', true).style('margin-bottom', '10px').attr('title', 'Export KML file of all explored tiles based on the current set of filters.');
                cg.append('label').classed('control-label', true).text('Explorer tile export:');
                var cgd = cg.append('div').classed('controls', true);
                cgd.append('a')
                    .attr({
                        'id': 'exportExploredKml',
                        'class': 'btn btn-primary btn-mini',
                        'style': 'color:white;margin-top:5px'
                    }).html('KML');
                cgd.append('span')
                    .attr({
                        'id': 'exportExploredKmlText',
                        'style': 'display:none;vertical-align:bottom;margin-left:5px;'
                    });

                cg = f.append('div').classed('control-group', true).style('margin-bottom', '10px').attr('title', 'Export KML file of all unexplored tiles in the current map view based on the current set of features.');
                cg.append('label').classed('control-label', true).text('Unexplored tile export:');
                var cgd = cg.append('div').classed('controls', true);
                cgd.append('a')
                    .attr({
                        'id': 'exportUnexploredKml',
                        'class': 'btn btn-primary btn-mini',
                        'style': 'color:white;margin-top:5px'
                    }).html('KML');
                cgd.append('span')
                    .attr({
                        'id': 'exportUnexploredKmlText',
                        'style': 'display:none;vertical-align:bottom;margin-left:5px;'
                    });

                d3.select('#map' + suffix + 'ConfigModal .modal-body')
                    .append('div').attr('id', 'explorerConfigModalInfo').classed('alert alert-info', true)
                    .html(explorerConfigInfoText);
                d3.select('#map' + suffix + 'ConfigModal .modal-body')
                    .append('div').classed('form-horizontal explorerConfig', true)
                    .selectAll('div.colourPickers').data(explorerConfigData)
                    .enter().append('div').classed('control-group', true).style('margin-bottom', '5px').each(function(d) {
                        if (d.id == 'explorerTileStrokeSat') d3.select(this).classed('satStart', true);
                        d3.select(this).append('label').classed('control-label', true).attr('for', d.id).html(d.desc + ':');
                        d3.select(this).append('div').classed('controls', true).each(function() {
                            d3.select(this).append('input').attr('type', 'text').attr('id', d.id);
                        });
                        $('#' + d.id).spectrum({
                            showAlpha: true,
                            change: updateExplorerColour,
                            //move: updateExplorerColour,  // saves every time so best not to use
                            allowEmpty: false,
                            showButtons: false,
                            preferredFormat: 'hex',
                            show: showExplorerColour,
                            appendTo: d3.select('#map' + suffix + 'ConfigModal .modal-body').node()
                        });
                        $('#' + d.id).spectrum('set', explorerConfig[d.id] ? explorerConfig[d.id] : d.default);
                    });
                d3.select('#map' + suffix + 'ConfigModal .modal-body .explorerConfig').insert('h3', '.satStart').text('Satellite map colour options:');
            }
        }
    } else {
        if (getParameterByName('zwift') == 1) {
            zwiftMaps.addTo(localMap);
        }
    }

    /*var btn = L.functionButtons([{ content: '<span style="color:red">Sqr</span>', bgColor: '#444' }, { content: 'Clu' }]);
    btn.on('clicked', function(data) {
        if (data.idx == 0) {
            localMap.setView([59.939, 30.315], 13);
            btn.setBgColor(0, null);
        } else {
            localMap.setView([53.3, -1.7], 9);
        }
    });
    localMap.addControl(btn);

    // start the map in South-East England
    localMap.setView(new L.LatLng(53.3, -1.7), 6);*/


    // draw explorer tiles
    var tileCoords = [];
    for (var i = 0; i <= 16384; i++) {
        if (i % 2 == 0) {
            tileCoords.push([tile2lat(i, 14), -180]);
            tileCoords.push([tile2lat(i, 14), 180]);
        } else {
            tileCoords.push([tile2lat(i, 14), 180]);
            tileCoords.push([tile2lat(i, 14), -180]);
        }
    }
    for (var i = 16384; i >= 0; i--) {
        if (i % 2 == 0) {
            tileCoords.push([-90, tile2long(i, 14)]);
            tileCoords.push([90, tile2long(i, 14)]);
        } else {
            tileCoords.push([90, tile2long(i, 14)]);
            tileCoords.push([-90, tile2long(i, 14)]);
        }
    }
    lm_tileLines = L.polyline(tileCoords, lm_currentBaseLayer.id == 'hybrid' ? explorerTileStyleSat : explorerTileStyle);

    localMap.on('zoomend', function() {
        lm_showHideTileLines();
        if (typeof(lm_onzoomend) !== 'undefined') lm_onzoomend();
    });

    localMap.on('baselayerchange', function(e) {
        //set cookie
        lm_currentBaseLayer = e.layer;
        setCookie('lmap', lm_currentBaseLayer.id, 365);
        d3.selectAll('.os-logo-control').remove();
    });

    if (typeof(setExtent) !== 'undefined') {
        if (cpref == 'sd') {
            if (suffix == '' || suffix == '3') {
                setExtent();
            }
        } else {
            setExtent(applyFilters(a));
        }
    }
}
