﻿var maplarge = {};
maplarge.lastInfo = null;
maplarge.log = false;
if (!window.console) {
    window.console = {};
    window.console.log = window.console.log || function () { };
}

//Utility Functions
function toTitleCase(str) {
    if (str == null || str == undefined) return '';
    str = str.toString();
    str = str.replace('�', 'u').replace('�', 'u');
    return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
}

function addCommas(nStr) {
    nStr += '';
    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
}

function FilterLLZ(nvp) {
    this.filterString = "";
    if (nvp.filter != undefined && nvp.filter != null) this.filterString = nvp.filter;

    this.zoom = 4;
    if (nvp.z != undefined && nvp.z != null) this.zoom = parseInt(nvp.z);

    this.lat = 33.81497672282066;
    if (nvp.lat != undefined && nvp.lat != null) this.lat = parseFloat(nvp.lat);

    this.lng = -100.3886631011962;
    if (nvp.lng != undefined && nvp.lng != null) this.lng = parseFloat(nvp.lng);
}

function GetNVPFromURL() {

    var nvp = new Object();
    var urlparts = window.location.href.split("?");
    if (urlparts.length < 2) return nvp;
    var q = urlparts[1].split("&");
    var count = q.length;
    for (var i = 0; i < count; i++) {
        var nameValue = q[i].split('='); //
        if (nameValue.length == 1) nvp[nameValue[0]] = null;
        else if (nameValue.length == 2) nvp[nameValue[0]] = unescape(nameValue[1]);
    }
    return nvp;
}

function GetFilterLLZ() {
    var nvp = GetNVPFromURL();
    var llz = new FilterLLZ(nvp);
    return llz;
}

function streetHTML(lat, lng, divID) {
    return scriptTagWrap(' streetInit(' + lat + ',' + lng + ',"' + divID + '"); ');
}

function scriptTagWrap(jstext) {
    return '<scr' + 'ipt type="tex' + 't/jav' + 'ascript"> '+jstext+' </sc' + 'ript>';
}

function streetInit(lat, lng, divID) {
    var lltmp = new google.maps.LatLng(lat, lng);
    var mapOptions = {
        center: lltmp,
        zoom: 17,
        mapTypeId: google.maps.MapTypeId.SATELLITE,
        streetViewControl: true
    };

    var tmpMap = new google.maps.Map(document.getElementById(divID), mapOptions);

    var panoramaOptions = {
        position: lltmp,
        pov: {
            heading: 34,
            pitch: 10,
            zoom: 1
        }
    };
    
    var panorama = new google.maps.StreetViewPanorama(document.getElementById(divID), panoramaOptions);
    tmpMap.setStreetView(panorama);
    
}





//Event Control
function MouseWatcher(map,clickableLayersTopFirst) {
    var me = this;
    this.map = map;
    this.moving = false;
    var layers = clickableLayersTopFirst;
    this.layerCount = layers.length;

    this.addLayers = function (layerArray) {
        var max = layerArray.length;
        for (var i = 0; i < max; i++) {
            layers.push(layerArray[i]);
        }
        me.layerCount = layers.length;
    }

   //Layer Click
    google.maps.event.addListener(map, 'click', function (event) {
        //alert(event.latLng + "," + map.getZoom());
        clickCheck(event.latLng.lat(), event.latLng.lng(), map.getZoom(),0);
    });


    google.maps.event.addListener(map, 'dragstart', function (event) {
        this.moving = true;
        me.moving = true;
        for (var i = 0; i < me.layerCount; i++) {
            layers[i].moving = true;
        }
    });

    this.moveTimeId = 0;
    google.maps.event.addListener(map, 'dragend', function (event) {
        me.moving = true;
        for(var i =0; i<me.layerCount; i++)
        {
            layers[i].moving = true;
        }

        

        if (this.moveTimeId != 0) clearTimeout(this.moveTimeId);
        this.moveTimeId = setTimeout(function () { me.allowClicks(); }, 1000);

    });

    this.allowClicks = function() {
        me.moving = false;
        for (var i = 0; i < me.layerCount; i++) {
            layers[i].moving = false;
        }
    }

    //used for on hover
    var msRepeat = 0;
    google.maps.event.addListener(map, 'mousemove', function (event) {
        var timeNow = new Date();
        var seconds = Math.floor(timeNow.getMilliseconds() / 100);
        if (msRepeat != seconds) {
            msRepeat = seconds;
            hoverCheck(event.latLng.lat(), event.latLng.lng(), map.getZoom(), 0);
        }
    });

    function clickCheck(lat, lng, zoom, layerIndex) {
        if (me.moving) return;
        if (layerIndex >= me.layerCount) return; //we're done exit

        if (layers[layerIndex].visible) {
            layers[layerIndex].mapClicked(lat, lng, zoom, clickCheck, layerIndex + 1); //ask layer to look for data if no data then continue
        } else {
            clickCheck(lat, lng, zoom, layerIndex + 1); //skip hidden layers
        }
        
    }

    function hoverCheck(lat, lng, zoom, layerIndex) {
        if (layerIndex >= me.layerCount) return; //we're done exit

        if (layers[layerIndex].visible) {
            layers[layerIndex].mapHover(lat, lng, zoom, hoverCheck, layerIndex + 1);
        } else {
            hoverCheck(lat, lng, zoom, layerIndex + 1);
        }

    }

}

function createTimeline(globalName, layer, baseFilter, filters, labels, timeClick, mouseWatcher) {
    
    var count = filters.length;
    var layers = [];
    for (var i = 0; i < count; i++) {
        var l = layer.clone(globalName + ".layers[" + i + "]");
        l.filterString += baseFilter + "|" +  filters[i];
        layers.push(l);
    }
    return new layerTimeline(globalName, layers, labels, timeClick, mouseWatcher);
}

function layerTimeline(globalName, layers, labels, timeClick, mouseWatcher) {
    this.timeClick = timeClick;
    this.layers = layers;
    this.labels = labels;
    this.map = layers[0].map;
    this.clicked = false;
    var me = this;
    //this.watcher = new MouseWatcher(this.map, layers);
    this.watcher = mouseWatcher;
    if (mouseWatcher != null) {
        mouseWatcher.addLayers(layers);
    }

    this.getHtml = function () {
        var count = this.layers.length;
        var widthPercent = Math.floor(100 / count);
        var c = new Array(count);
        var bg = "background-color:Black; color:White; border: 1px solid Black;";
        var bgSelected = "background-color:Red; color:White; border: 1px solid Black;";
        for (var i = 0; i < count; i++) {
            if (this.clicked && i == this.currentIndex) {
                c[i] = "<div style='float:left; text-align:center; " + bgSelected + " width:" + widthPercent + "%; height:100%; cursor:pointer;' onclick='javascript:" + globalName + ".show(" + i + ")' >" + this.labels[i] + "</div>";
            } else {
                c[i] = "<div style='float:left; text-align:center; " + bg + " width:" + widthPercent + "%; height:100%; cursor:pointer;' onclick='javascript:" + globalName + ".show(" + i + ")' >" + this.labels[i] + "</div>";
            }
        }
        return "<div style='width:100%; height:100%; " + bg + "' >" + c.join('') + " </div>";
    }

    //indexes match the array order
    this.show = function (layerIndex) {
        this.clicked = true;
        this.timeClick();
        var count = this.layers.length;
        for (var i = 0; i < count; i++) {
            layers[i].hide();
        }
        this.layers[layerIndex].show();
    }

    this.playID = 0;
    this.currentIndex = 0;
    this.secondsPerFrame = 5;
    this.play = function (secondsPerFrame) {
        if (me.currentIndex >= me.layers.length) me.currentIndex = 0;
        if (secondsPerFrame) me.secondsPerFrame = secondsPerFrame;
        me.showNext();
    }

    this.showNext = function(){
        if (me.playID != 0) clearTimeout(me.playID);
        if (me.currentIndex >= me.layers.length) return;
        
        me.show(me.currentIndex);
        me.currentIndex++;
        me.playID = setTimeout(me.showNext, me.secondsPerFrame * 1000);
    }

    this.pause = function () {
        if (this.playID != 0) clearTimeout(this.playID);
    }
}

var geo = new addressGeocoder();
function addressGeocoder() {
    //var me = this;
    var geocoder = {};// = new google.maps.Geocoder();
    this.geocodeZoomTo = null;
    this.onGeocodeFail = null;
    this.onGeocodeSuccess = null;
    this.geocodeZoomTo = null;
    this.map = null;
    this.geocode = function (map, inputDiv, geocodeZoomTo, onGeocodeSuccess, onGeocodeFail) {
    	if (!geocoder.geocode) geocoder = new google.maps.Geocoder();
    	this.onGeocodeFail = onGeocodeFail;
    	this.geocodeZoomTo = geocodeZoomTo;
    	this.map = map;
    	var address = document.getElementById("address").value;
    	geocoder.geocode({
    		'address': address,
    		'partialmatch': true
    	}, this.geocodeResult);
    }


    this.geocodeResult = function (results, status) {
    	
    	if (status == 'OK' && results.length > 0) {

    		if (this.onGeocodeSuccess == null && this.map != null) {
    			this.map.fitBounds(results[0].geometry.viewport);
    			if (this.geo.geocodeZoomTo) this.map.setZoom(this.geo.geocodeZoomTo);
    		}

    		if (this.onGeocodeSuccess != null) this.onGeocodeSuccess(results, status, this.map);

    	} else {
    		if (this.onGeocodeFail != null) {
    			onGeocodeFail(status);
    		} else {
    			alert("Geocode was not successful for the following reason: " + status);
    		}

    	}
    }


    this.reverseGeocode = function (lat, lng, map, callback) {
        if (!geocoder.geocode) geocoder = new google.maps.Geocoder();
        this.map = map;
        var latlng = new google.maps.LatLng(lat, lng);

        geocoder.geocode({ 'latLng': latlng }, 
            function (results, status) {
                if (status == google.maps.GeocoderStatus.OK && results[0]) {
                    callback(results[0].formatted_address);
                } else {
                    callback("");
                }
            });
    }

    this.reverseGeocodeAndInsertHTML = function (lat, lng, map, divID) {
        this.reverseGeocode(lat, lng, map, function (text) { document.getElementById(divID).innerHTML = text; });
    }
    
}


//maplarge.mobileRestore = {};//will hold css needed to revert back to normal view;
function mobileSmall(mapDivName,forceAll) {
  var useragent = navigator.userAgent;
  var url = window.location.href.toLowerCase();
  if (forceAll || useragent.indexOf('iPhone') != -1 || useragent.indexOf('Android') != -1 || url.indexOf('fullscreen=true') > -1) {
      if (window.location.href.toLowerCase().indexOf("fullscreen=false") > -1) return;
      $('html').attr("style", "height:100% !important; ");
      $('body').attr("style", "height:100% !important; margin:0px !important; padding:0px !important; position:relative !important; ");
      
//      $('html').css("height", "100%");
//      $('body').css("height", "100%");
//      $('body').css("margin", "0px");
//      $('body').css("padding", "0px");
//      $('body').css("position", "relative");
      
      $("#" + mapDivName).appendTo("body");
      $('<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />').appendTo('head');
      $('body').append("<div style='position:absolute; left:0px; font-size:12px; top:0px; width:100%; height:14px; color:white; background-color:black; cursor:pointer;' onclick='mobileRevert()' >Full Screen View (Switch)</div>");

      $("#" + mapDivName).attr("style", "position:absolute !important; width:100% !important; height:100% !important; z-index:999999 !important; top:14px !important; left:0px !important; ");

//      var mapdiv = document.getElementById(mapDivName);
//        mapdiv.style.width = '100%';
//        mapdiv.style.height = '100%';
//        mapdiv.style.position = 'absolute';
//        mapdiv.style.zindex = '999999';
//        mapdiv.style.top = '14px';
//        mapdiv.style.left = '1px';


    //google.maps.event.trigger(map, 'resize'); 
  }
}

function mobileRevert() {

    var loc = window.location.href.replace("fullscreen=true", "");
    var nore = "fullscreen=false";
    if (loc.indexOf("?") == -1) nore = "?" + nore;
    else nore = "&" + nore;
    loc = loc + nore;
    window.location.href = loc;
}

//Actual layers
function mapLayer(layerGlobalVarName, gMap, layerString, shaderString, filterString, hoverFieldsCommaDel, hoverFunc, clickFunc, listFunc, listMax, disableScrollBeforeClick) {

    try {
        var mapDivID = gMap.getDiv().id;
        mobileSmall(mapDivID);
        google.maps.event.trigger(map, 'resize'); 
    } catch (err) {
    }

    this.swaphost = false;
    try {
            
        if (GetNVPFromURL().swaphost) 
            this.swaphost = GetNVPFromURL().swaphost;
        
    } catch (err) {
        this.swaphost = false;
    }

    this.hoverDownShift = -80;
    this.hoverLeftShift = 50;
    this.map = gMap;
    //this way people who scroll up and down the page don't activate the scroll wheel zoom
    //once you click the map
    if (disableScrollBeforeClick == null) disableScrollBeforeClick = true;
    if (disableScrollBeforeClick) {
        this.enableScrollOnNextClick = true;

        this.map.setOptions({
            zoom: this.map.getZoom(),
            center: this.map.getCenter(),
            scrollwheel: false,
            mapTypeId: this.map.getMapTypeId(),
            mapTypeControl: true
        }); //disable scrolling by creating a new object with all the needed params 
    } else {
        this.enableScrollOnNextClick = false;
    }

    var self = this;
    this.layerString = layerString;
    this.shaderString = shaderString;
    this.filterString = filterString;

   if (self.layerString == undefined) self.layerString = '';
   if (self.shaderString == undefined) self.shaderString = '';
   if (self.filterString == undefined) self.filterString = '';

    this.moving = false;
    this.layerName = layerGlobalVarName;
    this.hoverImage = baseUrl() + "/clear.png"
    if (hoverFieldsCommaDel == undefined || hoverFieldsCommaDel == null) hoverFieldsCommaDel = "";
    if (listMax == undefined || listMax == null) listMax = 10;
    this.listMax = listMax;

    this.hoverFields = hoverFieldsCommaDel.toString().split(",");
    this.hoverSubFields = new Array(this.hoverFields.length);
    var parts;
    for (var fieldNum = 0; fieldNum < this.hoverFields.length; fieldNum++) {
        parts = this.hoverFields[fieldNum].split("~");
        if (parts.length > 1) {
            this.hoverSubFields[fieldNum] = { "name": parts[0], "op": parts[1] }; //FieldName / Operation
        } else {
            this.hoverSubFields[fieldNum] = false;
        }
    }

    this.hoverFunc = hoverFunc;
    this.clickFunc = clickFunc;
    this.listFunc = listFunc;

    this.visible = false;
   

    
    //LISTS on move end  tilesloaded--> good trigger for most move end events
    if (listFunc != undefined && listFunc != null) {
        
        google.maps.event.addListener(this.map, 'tilesloaded', function (event) {
            self.getList(self.map.getBounds(), self.map.getZoom());
        });
    }

    this.getLayerString = function () {
        return "shader=" + self.shaderString + "&layer=" + self.layerString + "&filter=" + self.filterString;
    }




    var layerOptions = {
        getTileUrl: function (xy, zoom) {
            var coord = {};
            coord.x = xy.x;
            coord.y = xy.y; //do this so we can modify the coord object

            //            if (coord.x < 0) {
            //                while(coord.x < 0) coord.x += (1 << zoom)
            //            }
            //            coord.x = coord.x % (1 << zoom); 
            var numTiles = 1 << zoom;
            // Don't wrap tiles vertically.
            if (coord.y < 0 || coord.y >= numTiles) {
                return null;
            }

            // Wrap tiles horizontally.
            coord.x = ((coord.x % numTiles) + numTiles) % numTiles;

            var url = '';
            try {
                url = window.location.href.toString();
            } catch (err) {
                url = '';
            }

            //http://localhost:2493
            if (self.swaphost) {
                return self.swaphost + '/Tile/Tile?layer=' + self.layerString + '&x=' + coord.x + '&y=' + coord.y + '&z=' + zoom + '&filter=' + self.filterString + "&shader=" + self.shaderString;
            }else if (window._forceLocal ) {
                return window._forceLocal + '/Tile/Tile?layer=' + self.layerString + '&x=' + coord.x + '&y=' + coord.y + '&z=' + zoom + '&filter=' + self.filterString + "&shader=" + self.shaderString;
            }
            else if (url.indexOf("cloudapp.net") > -1) {
                return url.split("cloudapp.net")[0] + 'cloudapp.net/Tile/Tile?layer=' + self.layerString + '&x=' + coord.x + '&y=' + coord.y + '&z=' + zoom + '&filter=' + self.filterString + "&shader=" + self.shaderString;
            }
            else {
                return 'http://' + (coord.x % 4) + 'api.maplarge.com' + '/Tile/Tile?layer=' + self.layerString + '&x=' + coord.x + '&y=' + coord.y + '&z=' + zoom + '&filter=' + self.filterString + "&shader=" + self.shaderString;
            }
        },
        tileSize: new google.maps.Size(256, 256),
        isPng: true
    };

    var layerMapType = new google.maps.ImageMapType(layerOptions);



    //this actually adds the layer to the map
    this.show = function () {
        this.visible = true;
        //don't add layer twice
        var layers = this.map.overlayMapTypes;
        var thisLayer = layerMapType;
        layers.forEach(function (layer, index) {
            if (layer == thisLayer) return;
        });

        this.map.overlayMapTypes.push(layerMapType);

//        if (listFunc != undefined && listFunc != null) {
//            //google.maps.event.addListener(this.map, 'tilesloaded', function (event) {
//            getList(self.map.getBounds(), self.map.getZoom());
//            //});
//        }
    };

    this.hide = function () {
        this.visible = false;
        var layers = this.map.overlayMapTypes;
        var thisLayer = layerMapType;
        layers.forEach(function (layer, index) {

            if (layer == thisLayer) {
                if (self.lastMarker) self.lastMarker.setMap(null);
                if (self.lastInfo != null) self.lastInfo.close();
                self.map.overlayMapTypes.removeAt(index);
            }
            //poly.getPath().forEach(function(latLng) {bounds.extend(latLng);
        });
    };


    function baseUrl() {

        var url = '';
        try {
            url = window.location.href.toString();
        } catch (err) {
            url = '';
        }

        if (self.swaphost) {
            return self.swaphost;
        }else if (window._forceLocal) {
            return window._forceLocal;
        }else if (url.indexOf("cloudapp.net") > -1) {
            return url.split("cloudapp.net")[0] + "cloudapp.net";
        } 
        else {
            return 'http://0api.maplarge.com';
       }

    }

    var legend = null;
    this.getLegendColors = function (divID, callback) {
        var me = this;
        $.ajax({
            url: baseUrl() + "/Tile/GetLegendData?shader=" + self.shaderString + "&layer=" + self.layerString + "&callback=?",
            dataType: 'json',
            success: function (data) {

                if (divID != null && divID != undefined && divID != "") {
                    document.getElementById(divID).innerHTML = me.makeLegendHTML(data, me);
                }


                if (callback != null && callback != undefined) {
                    callback(data, me);
                }


            }
        });

    }

//    this.makeLegendHTML = function (data, layer) {
//        var colors = data.colors;
//        var count = colors.length;
//        var widthPercent = Math.ceil(100 / count);
//        var c = new Array(count);
//        var bg = "";
//        for (var i = 0; i < count; i++) {
//            bg = "background-color:rgb(" + colors[i].R + "," + colors[i].G + "," + colors[i].B + ");";
//            c[i] = "<div style='float:left; " + bg + " width:" + widthPercent + "%; height:100%; '></div>";
//        }
//        //document.getElementById(divID).innerHTML = "<div style='width:100%; height:100%; " + bg + "' >" + c.join('') + " </div>";
//        return "<div style='width:100%; height:100%; " + bg + "' >" + c.join('') + " </div>";
//    }



    this.makeLegendHTML = function (data, layer) {
        var colors = data.colors;
        var count = colors.length;
        //Need to get to exactly 1% width divs
        var hundred = [];
        if (count < 100) {

            var missingPercent = (100 - count) / 100; //ex 37% missing.. means add about one extra for each 3
            var add = 0;
            var lastAdd = 0;
            for (var i = 0; i < count; i++) {
                hundred.push(colors[i]);
                if (Math.floor(add) > lastAdd) {
                    hundred.push(colors[i]);
                    lastAdd = Math.floor(add);
                }
                add += missingPercent;
            }

        } else if (count > 100) {

            var skipPerColor = count / 100; //ex 200/100 = 2.5.. skip 2.5 for each one we add
            var needToSkip = skipPerColor;
            var remainder = 0;
            var nextAdd = 0;
            for (var i = 0; i < count; i++) {
                if (Math.floor(needToSkip) == 0) {
                    hundred.push(colors[i]);
                    remainder = needToSkip - Math.floor(needToSkip);
                    needToSkip = remainder + skipPerColor;
                }
                needToSkip -= 1;
            }

        } else {
            hundred = colors;
        }
        //var widthPercent = Math.ceil(100 / count);

        count = hundred.length;
        var c = new Array(count);
        var bg = "";
        for (var i = 0; i < count; i++) {
            bg = "background-color:rgb(" + hundred[i].R + "," + hundred[i].G + "," + hundred[i].B + ");";
            c[i] = "<div style='float:left; " + bg + " width:" + 1 + "%; height:100%; '></div>";
        }
        return "<div style='width:100%; height:100%; " + bg + "' >" + c.join('') + " </div>";
    }








    //==After user pans then enable scroll wheel zoom
    this.firstMove = true;
    this.getList = function (bounds, zoom) {
        if (listFunc == null || listFunc == undefined) return;

        if (this.firstMove == false) {
            if (this.enableScrollOnNextClick) {

                this.enableScrollOnNextClick = false;

                this.map.setOptions({
                    zoom: this.map.getZoom(),
                    center: this.map.getCenter(),
                    scrollwheel: true,
                    mapTypeId: this.map.getMapTypeId(),
                    mapTypeControl: true
                }); //disable scrolling by creating a new object with all the needed params 


            }
        } else {
            this.firstMove = false;
        }

        //Need to add an "attach list" and detacth list function
        var min = bounds.getSouthWest();
        var max = bounds.getNorthEast();
        //typeof myFunc === 'function'
        // var me = this;
        $.ajax({
            url: baseUrl() + "/Tile/GetVisibleList?shader=" + self.shaderString + "&layer=" + self.layerString + "&z=" + zoom +
            "&max=" + listMax + "&minLat=" + min.lat() + "&maxLat=" + max.lat() + "&minLng=" + min.lng() + "&maxLng=" + max.lng() + '&filter=' + self.filterString + "&callback=?",
            dataType: 'json',
            success: function (data) {

                var max = data.rows.length;
                var rows = data.rows;
                var clickStrings = new Array(max);
                var hoverStrings = new Array(max);

                var hf = self.hoverFields;
                var count = hf.length;
                //bacause these can be calculated totals with ~sum etc we have to normalize that
                var hf = this.hoverFields;
                var subFields = this.hoverSubFields;

                //{fieldName1:[{avg:111,sum:222,count:333},{avg:111,sum:222,count:333}],fieldName2:[{avg:111,sum:222,count:333},{avg:111,sum:222,count:333}]}
                //var subTotals = data.subTotals;

                var toolTipString = "";
                var thisLayerName = self.layerName;
                var tmp = "";
                var hoverHtml = new Object();
                for (var i = 0; i < max; i++) {


                    index = rows[i].index;
                    //click string
                    rows[i]["clickString"] = "'" + thisLayerName + ".mapClickedIndex(" + index + ");'";

                    //hover string
                    if (hoverFunc != null) {
                        toolTipString = self.hoverFunc(rows[i], self);
                    } else {
                        var tmp = [];
                        for (var fieldNum = 0; fieldNum < count; fieldNum++) {

                            if (hf == undefined) continue;
                            if (hf[fieldNum] == "") continue;
                            if (subFields[fieldNum] == false) {
                                tmp.push("<b>" + hf[fieldNum] + ":</b> " + rows[i].data[hf[fieldNum]]);
                            } else {//original name LowRate~avg
                                var subF = subFields[fieldNum];
                                var subfname = subFields[fieldNum].name;
                                var subOp = subFields[fieldNum].op;
                                tmp.push("<b>" + hf[fieldNum] + ":</b> " + rows[i].subTotals[subfname][subOp]); //ex SubTotals[LowRate][i][avg];
                            }

                        }

                        //toolTipString = tmp.join("<br/>").replace("'", "").replace('"', "");
                        toolTipString = tmp.join("<br/>"); //.replace("'", "").replace('"', "");
                    }
                    self.markSpotHTML[index] = toolTipString;
                    //tmp = "'" + thisLayerName + ".markSpot(" + rows[i].lat + "," + rows[i].lng + ",\"" + toolTipString + "\"," + index + ");'";
                    //rows[i]["hoverString"] = tmp.replace("'", "\'");
                    tmp = "'" + thisLayerName + ".markSpot(" + rows[i].lat + "," + rows[i].lng + "," + index + ");'";
                    rows[i]["hoverString"] = tmp;
                }

                listFunc(data, self);

            }
        });


    };



    this.markSpotHTML = new Object();
    //this.markSpot = function (lat, lng, toolTipString, rowIndex) {
    this.markSpot = function (lat, lng, rowIndex) {
        if (this.lastMarker) { this.lastMarker.setMap(null); }

        var loc = new google.maps.LatLng(lat, lng); //actual location of the dot
        var zoom = this.map.getZoom();
        var html = this.markSpotHTML[rowIndex];
        
        this.lastMarker = new CustomMarker(loc, this.map,
                "<div style='width:64px; height:64px; background-color:transparent; cursor:pointer; background-image:url(\"" + this.hoverImage + "\"); background-repeat:no-repeat; ' " +
                  " onclick='" + this.layerName + ".mapClickedIndex(" + rowIndex + "); return true;'  > " +
            "<img alt='' src='" + baseUrl() + "/Tile/GetIcon/?shader=" + self.shaderString + "&layer=" + self.layerString + "&z=" + zoom + "&rowIndex=" + rowIndex + "' style='cursor:pointer; position:absolute; background-color:transparent; left:32px; top:32px;' /> " +
            "<div style='cursor:pointer; position:absolute; left:50px; top:-80px; background-color:White; border:1px solid Gray;'> " +
             html + "</div></div>");

    };

    this.getIcon = function (data, zoom) {
        return baseUrl() + "/Tile/GetIcon/?shader=" + self.shaderString + "&layer=" + self.layerString + "&z=" + zoom + "&rowIndex=" + data.index;
    }   

    //not called by click watcher
    this.mapClickedIndex = function (index) {

        if (this.enableScrollOnNextClick) {
            
            this.enableScrollOnNextClick = false;

            this.map.setOptions({
                zoom: this.map.getZoom(),
                center: this.map.getCenter(),
                scrollwheel: true,
                mapTypeId: this.map.getMapTypeId(),
                mapTypeControl: true
            }); //disable scrolling by creating a new object with all the needed params 


        }

        var me = this;
        if (this.moving) return;
        $.ajax({
            url: baseUrl() + "/Tile/ClickIndex?shader=" + self.shaderString + "&layer=" + self.layerString + "&filter=" + self.filterString + '&index=' + index + "&callback=?",
            dataType: 'json',
            success: function (data) {

                //no data fuond
                if (!data || !data.data) {
                    //because we're using index here we don't need the layered callback function
                    //if(nextLayerFunc != null) nextLayerFunc(nextLayerIndex); //if no data found call next layer
                    return;
                }

                var cols = data.data;
                var subCols = data.subTotals;

                var zoom = me.map.getZoom();

                var itemHtml = ""; // +me.layerName;
                if (clickFunc == null || clickFunc == undefined) {
                    var items = [];
                    items.push("Layer: " + me.layerName + ": ");
                    for (var c in cols) {
                        items.push("<b>" + c + "</b>: " + cols[c]);
                    }
                    if (subCols != null && subCols != undefined) items.push("<b>--SubTotals--</b>");
                    for (var c in subCols) {
                        items.push("<b>" + c + "-Avg</b>: " + subCols[c].avg);
                        items.push("<b>" + c + "-Sum</b>: " + subCols[c].sum);
                        items.push("<b>" + c + "-Count</b>: " + subCols[c].count);
                    }

                    itemHtml = "<div style='font-size:small; height:300px;'>" + items.join("<br/>") + "</div>";
                } else {
                    itemHtml = clickFunc(data, this);
                }

                var infowindow = new google.maps.InfoWindow({ content: itemHtml });

                if (lastInfo != null) lastInfo.close();
                lastInfo = infowindow;

                if (lastInfo != null) lastInfo.close();
                lastInfo = infowindow;
                maplarge.lastInfo = infowindow;

                var loc = new google.maps.LatLng(data.lat, data.lng);
                infowindow.setPosition(loc);
                infowindow.open(me.map);

            }
        });

    };

    var randIncrement = 0;
    var lastInfo = null;
    this.mapClicked = function (lat, lng, zoom, nextLayerFunc, nextLayerIndex) {

        if (this.enableScrollOnNextClick) {

            this.enableScrollOnNextClick = false;

            this.map.setOptions({
                zoom: this.map.getZoom(),
                center: this.map.getCenter(),
                scrollwheel: true,
                mapTypeId: this.map.getMapTypeId(),
                mapTypeControl: true
            }); //disable scrolling by creating a new object with all the needed params 


        }

        if (this.moving) return;
        if (this.lastMarker != false && this.lastMarker != null) {
            return;  //this gives the Index based click priority so we don't doulble open the balloon
        }
        var me = this;
        randIncrement += 1;
        $.ajax({
            url: baseUrl() + "/Tile/Click?shader=" + self.shaderString + "&layer=" + self.layerString + "&lat=" + lat + "&lng=" + lng + "&z=" + zoom + '&filter=' + self.filterString + "&rand=" + randIncrement + "&callback=?",
            dataType: 'json',
            cache: true,
            success: function (data) {

                //alert("test");
                //no data fuond
                if (!data || !data.data) {
                    //because we're using index here we don't need the layered callback function
                    if (nextLayerFunc != null) nextLayerFunc(lat, lng, zoom, nextLayerIndex); //if no data found call next layer
                    return;
                }

                var cols = data.data;
                var subCols = data.subTotals;

                zoom = me.map.getZoom();

                var itemHtml = ""; // +me.layerName;
                if (clickFunc == null || clickFunc == undefined) {
                    var items = [];
                    items.push("Layer: " + me.layerName + ": ");
                    for (var c in cols) {
                        items.push("<b>" + c + "</b>: " + cols[c]);
                    }
                    if (subCols != null && subCols != undefined) items.push("<b>--SubTotals--</b>");
                    for (var c in subCols) {
                        items.push("<b>" + c + "-Avg</b>: " + subCols[c].avg);
                        items.push("<b>" + c + "-Sum</b>: " + subCols[c].sum);
                        items.push("<b>" + c + "-Count</b>: " + subCols[c].count);
                    }

                    itemHtml = "<div style='font-size:small;'>" + items.join("<br/>") + "</div>";
                } else {
                    itemHtml = clickFunc(data, this);
                }

                var infowindow = new google.maps.InfoWindow({ content: itemHtml });

                if (lastInfo != null) lastInfo.close();
                lastInfo = infowindow;
                maplarge.lastInfo = infowindow;
                var loc = new google.maps.LatLng(data.lat, data.lng);
                infowindow.setPosition(loc);
                infowindow.open(me.map);

            }
        });

    };

    function roundTo(number, decimals) {
        var power = Math.pow(10,decimals);
        return Math.round(number * power) / power;
    }

    //var hoverCache = new Object();
//    this.clearMarkers = function() {

//    }
    this.hoverHide = null;
    var lastSec = 0;
    //var cursor = container.style.cursor;
    this.lastMarker = false;
    var lastrowIndex = 0;
    var hoverGrids = new Object();
    var hoverData = new Object();
    this.hoverTimeId = 0;
	this.uniqueImgID = 0;
	this.mapHover = function (lat, lng, zoom, nextLayerFunc, nextLayerIndex) {
	    var me = this;
	    var pix20x = LngToX(lng); //pixel zoom at 20
	    var pix20y = LatToY(lat);

	    var x = mapToZoom(pix20x, zoom); //pixel at current zoom
	    var y = mapToZoom(pix20y, zoom);

	    var TILESIDE = 256;
	    var tileX = Math.floor(x / TILESIDE); // x coord of the tile (i think the tildas round)
	    var tileY = Math.floor(y / TILESIDE); // y coord of the tile
	    var inTileX = x % TILESIDE; // x coord inside the tile
	    var inTileY = y % TILESIDE; // y coord inside the tile

	    var xyz = tileX + "_" + tileY + "_" + zoom;
	    if (hoverGrids[xyz]) {

	        if (hoverGrids[xyz].loading) return;

	        var cellSize = 8;
	        var cellWH = 256 / cellSize;
	        // var xCell = Math.floor(inTileX / 8);
	        var xCell = Math.floor(inTileX / cellSize);
	        var yCell = Math.floor(inTileY / cellSize);
	        var gridIndex = Math.floor((yCell * cellWH) + xCell);
	        var grid = hoverGrids[xyz].grid;
	        var rowIndex = grid.index[gridIndex];
	        if (maplarge.log)
	            console.log(rowIndex);

	        if (rowIndex == lastrowIndex && rowIndex != -1) return;
	        if (rowIndex == lastrowIndex && rowIndex == -1) {
	            nextLayerFunc(lat, lng, zoom, nextLayerIndex); //call next hover function (allows for multiple layers
	            return;
	        }
	        lastrowIndex = rowIndex;
	        if (this.lastMarker) {
	            this.lastMarker.setMap(null);
	        }
	       
	        if (rowIndex == -1) {
	            if (me.hoverHide) {
	                me.hoverHide(); //allows external hover followers to know when over nothing
	            }
	            this.lastMarker == null;
	            nextLayerFunc(lat, lng, zoom, nextLayerIndex); //call next hover function (allows for multiple layers
	            return;
	        }


	        var row = grid.data[rowIndex + ""];
	        var leftShift = self.hoverLeftShift;
	        var downShift = self.hoverDownShift;
	        var toolTipString = "";
	        if (hoverFunc != null) {
	            var hoverText = row[2].split(","); //just values no field names order is implicit
	            var wrapper = new Object();
	            var data = new Object();
	            var subTotals = new Object();
	            var count = this.hoverFields.length;
	            var hf = this.hoverFields;
	            var subFields = this.hoverSubFields;
	            for (var i = 0; i < count; i++) {

	                if (subFields[i] == false) {
	                    data[hf[i]] = hoverText[i];
	                } else {
	                    if (subTotals[subFields[i].name] == undefined) subTotals[subFields[i].name] = new Object();
	                    subTotals[subFields[i].name][subFields[i].op] = hoverText[i]; //ex SubTotals[LowRate][avg];
	                }
	            }
	            wrapper.index = rowIndex;
	            wrapper.data = data;
	            wrapper.subTotals = subTotals;
	            wrapper.lat = lat;
	            wrapper.lng = lng;
	            toolTipString = this.hoverFunc(wrapper, me);
	        } else {
	            //toolTipString = row[2]; //by default just use the basic label
	            toolTipString = "";
	            var hoverText = row[2].split(",");
	            var hoverObject = new Object();
	            var count = this.hoverFields.length;
	            for (var i = 0; i < count; i++) {
	                toolTipString += "<b>" + this.hoverFields[i] + ":</b> " + hoverText[i] + "<br/>";
	            }
	            toolTipString = " <div style='width:150px;' > " + toolTipString + " </div> ";
	            leftShift = 150;
	        }

	        var dotLat = row[0];
	        var dotLng = row[1];
	        me.uniqueImgID++;
	        me.hoverimageid = 'hoverimage' + me.uniqueImgID;
	        var loc = new google.maps.LatLng(dotLat, dotLng); //actual location of the dot
	        var display = " ";
	        me.imagehtml = "<img id='" + me.hoverimageid + "' alt='' src='" + baseUrl() + "/Tile/GetIcon/?shader=" + self.shaderString + "&layer=" + self.layerString + "&z=" + zoom + "&rowIndex=" + rowIndex + "' style=' cursor:pointer; background-color:transparent;  position:absolute; left:32px; top:32px;' /> ";
	        var imageNow = me.imagehtml;
	        if (me.layerString.indexOf("dot") > -1 && me.layerString.indexOf("XY") > -1) {
	            imageNow = "<div id='" + me.hoverimageid + "'></div>";
	        }



	        var html = "<div style='width:64px; height:64px; background-color:transparent; cursor:pointer; background-image:url(\"" + me.hoverImage + "\"); background-repeat:no-repeat; ' " +
                  " onclick='" + this.layerName + ".mapClickedIndex(" + rowIndex + "); return true;'  > " +
            imageNow +
            "<div style='cursor:pointer; position:absolute;  left:" + leftShift + "px; top:" + downShift + "px; background-color:White; border:1px solid Gray; '> " +
             toolTipString + "</div></div>";

	        me.lastMarker = new CustomMarker(loc, me.map, html);


	        if (me.layerString.indexOf("dot") > -1 && me.layerString.indexOf("XY") > -1) {
	            var img = new Image();
	            img.src = "ht" + "tp://api.maplarge.com/Tile/GetIcon/?shader=" + me.shaderString + "&layer=" + me.layerString + "&z=" + zoom + "&rowIndex=" + rowIndex;
	            if (img.complete && img["width"]) {
	                setTimeout(function () { me.positionHoverImage(img, me.hoverimageid, me.imagehtml); }, 100);
	            } else {
	                //img.onload = function () { ShowHoverImage(mp.y, mp.x, map.getLevel(), evt, img); };
	                img.onload = function () { me.positionHoverImage(img, me.hoverimageid, me.imagehtml); };
	            }
	        }

	        //            if (me.hoverTimeId != 0) clearTimeout(me.hoverTimeId);

	        //            me.hoverTimeId = setTimeout(function () {
	        //                    if (me.lastMarker) {
	        //                        me.lastMarker.setMap(null);
	        //                    }
	        //                    me.lastMarker = new CustomMarker(loc, me.map, html);
	        //                }
	        //                , 50);

	    } else {

	        hoverGrids[xyz] = new Object();
	        hoverGrids[xyz].loading = true;

	        $.ajax({
	            url: baseUrl() + "/Tile/HoverGrid?shader=" + self.shaderString + "&layer=" + self.layerString + "&x=" + tileX + "&y=" + tileY + "&z=" + zoom + '&filter=' + self.filterString +
                '&label=' + hoverFieldsCommaDel + "&callback=?",
	            dataType: 'json',
	            cache: true,
	            success: function (data) {
	                //This is the callback for when the HoverGrid is downloaded
	                if (!data) return;
	                var gridKey = xyz;
	                hoverGrids[xyz].grid = data;
	                hoverGrids[xyz].loading = false;
	            }
	        });
	    }

	};

	this.positionHoverImage = function (img, id, html) {
		//left:32px; top:32px;
		var pos = "left:" + (32 - img.width / 2) + "px; top:" + (32 - img.height / 2) + "px;"
		html = html.replace("left:32px; top:32px;", pos);
		var el = document.getElementById(id);
		if(el) el.innerHTML = html;

		//    	$("#" + id).css("left",  (32 - img.width / 2) + "px");
		//    	$("#" + id).css("top", (32 - img.height / 2) + "px");
		//    	$("#" + id).css("display", "block");
	}

    this.clone = function (globalName) {
        //a clean clone can't have the same name in the global space.. sometimes its ok but here people have the option
        return new mapLayer(globalName, map, self.layerString, self.shaderString, self.filterString, self.hoverFields, self.hoverFunc, self.clickFunc, self.listFunc, self.listMax);
    }

//    function toolTipAfter(loc, map, html) {
//        
//    }


    function LngToX(lng) { return (1 + lng / 180); }

    function LatToY(lat) {
        var sinofphi = Math.sin(lat * Math.PI / 180);
        return (1 - 0.5 / Math.PI * Math.log((1 + sinofphi) / (1 - sinofphi)));
    }

    function XToLng(x) { // return (x-1)*180;
        return (x / 268435456 - 1) * 180; //pixels at zoom 20(gk note)
    }

    function YToLat(y) {
        return (Math.PI / 2 - 2 * Math.atan(Math.exp((Math.round(y) - 268435456) / (268435456 / Math.PI)))) * 180 / Math.PI;
    }

    function mapToZoom(c, zoom) {
        return ~ ~(0.5 + c * (2 << (zoom + 6)));
    }

//    function toTitleCase(str) {
//        str = str.toString();
//        return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
//    }

    this.zoomTo = function (lat, lng, zoom) {
        var ll = new google.maps.LatLng(lat, lng);
        this.map.setCenter(ll);
        this.map.setZoom(zoom);
    }



    //===Custom Marker Code
    function CustomMarker(latlng, map, html) {

        this.latlng_ = latlng;
        this.myhtml = html;
        //this.div_ = null;
        // Once the LatLng and text are set, add the overlay to the map.  This will
        // trigger a call to panes_changed which should in turn call draw.
        this.setMap(map);
    }

    CustomMarker.prototype = new google.maps.OverlayView();

    CustomMarker.prototype.draw = function () {
        var me = this;

        // Check if the div has been created.
        var div = this.div_;
        if (!div) {
            div = this.div_ = document.createElement('DIV');
            div.style.border = "none";
            div.style.position = "absolute";
            div.style.paddingLeft = "0px";
            div.style.cursor = 'pointer';
            div.style.backgroundcolor = 'White';
            div.style.width = "64px";
            div.style.height = "64px";
            div.innerHTML = this.myhtml;

            var panes = this.getPanes();
            panes.overlayImage.appendChild(div);
            div.innerHTML = this.myhtml;
        }

        // Position the overlay 
        var point = this.getProjection().fromLatLngToDivPixel(this.latlng_);
        if (point) {
            div.style.left = (point.x - 32) + 'px';
            div.style.top = (point.y - 32) + 'px';
        }
    };

    CustomMarker.prototype.remove = function () {
        // Check if the overlay was on the map and needs to be removed.
        if (this.div_) {

            if (this.div_.parentNode) this.div_.parentNode.removeChild(this.div_);
            this.div_ = null;
            this.html = null;
        }
    };

    CustomMarker.prototype.getPosition = function () {
        return this.latlng_;
    };



}


function findDebugURL() {

    try {
        var nvp = GetNVPFromURL();
        if (nvp.mldebugurl) {
            window._forceLocal = nvp.mldebugurl;
        }

    } catch (ex) {
    }
}
findDebugURL();


