﻿/// <reference path="GMAPJSHelper_Release.js" />
/// <reference path="jquery-1.3.2-vsdoc.js" />
/// <reference path="json2.js" />
/// <reference path="labeledmarker.js" />
/// <reference path="markerclusterer.js" />

//http://code.google.com/apis/maps/documentation/overlays.html#Icons_overview
//http://code.google.com/p/gmaps-utility-library-dev/

var OverlayType = { 'Marker': 1, 'Polyline': 2, 'Polygon': 3 };
var LayerType = { 'Overlay': 1, 'Tile': 2, 'GeoXml': 3 };
var GEOTHRESHOLD = 250; //max number of markers/geos per layer
var MAX_INITIAL_ZOOM = 19; //max number of markers/geos per layer
var MIN_CLUSTER_SIZE = 5; //5px x 5px cluster size
var ENCODER_zoomFactor = 18;
var ENCODER_numLevels = 3;
var CLUSTER_SHIFT = 0.0003;
var MIN_CLUSTER_EXPAND_ZOOM = 13;

function GetRandomHtmlColor() {
    var red = Math.floor(Math.random() * 255);
    var green = Math.floor(Math.random() * 255);
    var blue = Math.floor(Math.random() * 255);
    return '#' + red.toString(16) + green.toString(16) + blue.toString(16);
}
MapOverlay = function(id, gOverlay, type, subid) {
    this.Id = id;
    this.SubId = subid || 0;
    this.GOverlay = gOverlay;
    this.Type = type || OverlayType.Marker;
    this.OutOfBounds = false;

    this.ClusterInfo = { group: -1, latlng: null };

    this.onMap = false;
    //this.isClustered = false;
    //this.clusterCount = 0;
}
MapOverlay.prototype = {
    getCenterLatLng: function() {
        var o = this.GOverlay;
        if (this.Type == OverlayType.Marker)
            return o.getLatLng();
        else if (this.Type == OverlayType.Polyline)
            return o.getBounds().getCenter();
        else if (this.Type == OverlayType.Polygon)
            return o.getBounds().getCenter();
    }
}
BoundingBox = function(swlat, swlng, nelat, nelng) {
    this.SwLat = swlat;
    this.SwLng = swlng;
    this.NeLat = nelat;
    this.NeLng = nelng;
}
MapLayer = function(name, wsUrl, param, opts) {
    this.Name = name;
    this.WebServiceUrl = wsUrl;
    this.Param = param;
    this.jsonParam = JSON.stringify(param);

    if (typeof (opts) == "undefined")
        opts = {};

    this.Group = opts.group;

    if (typeof (opts.visible) == "undefined")
        opts.visible = true;
    this.Visible = opts.visible;

    this.Center = opts.center || false;

    this.FilterByMapBounds = opts.filterMb || false;

    if (typeof (opts.cluster) == "undefined")
        opts.cluster = true;
    this.Cluster = opts.cluster;

    this.AutoLabel = opts.autoLabel || false;

    this.MinVisibleZoom = opts.minVisibleZoom || 0; //0 is highest, 19 is closer
    this.MaxClusterZoom = opts.maxClusterZoom || 0; //max zoom that we should perform our own clustering
    this.ClusterGridSize = opts.clusterGridSize || 60; //60px x 60px
    this.ClusterPaging = opts.clusterPaging || false; //60px x 60px
    this.DistinctEntry = opts.distinctEntry || false;

    //this.MarkerClusterer = null;
    this.MarkerOptions = {};
    //this.CustomClusterMarkerOptions = {};
    this.ClusterMarkerOptions = {};

    this.Overlays = new Array();
    this.ClusterOverlays = new Array();

    this.enabled = true;
    this.type = LayerType.Overlay;
}
MapLayer.prototype = {
    bind: function(crgmap, callback) {
        var gmap = crgmap.GMap;
        var self = this;

        if (crgmap.onLayersLoadStart)
            crgmap.onLayersLoadStart.call(this);

        this.Param.maxResults = crgmap.MaxGeoPerLayer;
        this.Param.zoomLevel = gmap.getZoom();
        this.Param.distinctEntry = this.DistinctEntry;
        if (this.isClustering(gmap)) {
            this.Param.maxClusterZoom = this.MaxClusterZoom;
            this.Param.clusterGridSize = this.ClusterGridSize;
        }
        else {
            this.Param.clusterGridSize = crgmap.MinClusterSize;
        }

        if (this.FilterByMapBounds) {
            var bb = crgmap.getBoundingBox();
            this.Param.swLat = bb.SwLat;
            this.Param.swLng = bb.SwLng;
            this.Param.neLat = bb.NeLat;
            this.Param.neLng = bb.NeLng;
        }

        this.jsonParam = JSON.stringify(this.Param);

        //$.getJSON(this.WebServiceUrl, this.Param,
        //  function(data, textStatus) {

        $.ajax({
            url: this.WebServiceUrl,
            dataType: 'jsonp',
            data: this.Param,
            jsonp: 'method',
            //jsonpCallback: 'method',
            success: function(data, textStatus) {

                var data = data.d;

                var count;
                if (typeof (data.Count) == "undefined") {
                    count = data.results.length;
                }
                else {
                    count = data.Count;
                }

                if (count <= crgmap.MaxGeoPerLayer) {

                    //Clear all custom clustered markers.
                    self.removeClusteredOverlays(crgmap);

                    if (typeof (data.ClusterMarkers) != "undefined" && data.ClusterMarkers != null) {

                        //var newId = 0;
                        //var oldId = 0;
                        var labelCount = 1;

                        for (var i = 0; i < data.ClusterMarkers.length; i++) {
                            var cm = data.ClusterMarkers[i];

                            var isClustering = self.isClustering(gmap);

                            if (isClustering) {
                                var ll = new GLatLng(cm.LatLng.Lat, cm.LatLng.Lng);
                                self.ClusterMarkerOptions.labelText = cm.Count;
                                var marker = new LabeledMarker(ll, self.ClusterMarkerOptions);

                                var mo = new MapOverlay(0, marker, OverlayType.Marker);

                                self.addOverlay(crgmap, mo, true);
                            }
                            else {
                                //Check if Markers are too close together, then use a different info window popup
                                if (cm.Markers.length > 1 && this.ClusterPaging) {
                                    var ll = new GLatLng(cm.LatLng.Lat, cm.LatLng.Lng);
                                    //onClusterMarkerClick
                                }
                                else {
                                    var isCluster = false;
                                    if (cm.Markers.length > 1)
                                        isCluster = true;

                                    for (var j = 0; j < cm.Markers.length; j++) {
                                        var m = cm.Markers[j];
                                        var ll = new GLatLng(m.LatLng.Lat, m.LatLng.Lng);

                                        if (isCluster) {
                                            var ll = new GLatLng(m.LatLng.Lat, m.LatLng.Lng);
                                        }

                                        if (self.AutoLabel)
                                            self.MarkerOptions.labelText = labelCount; //String.fromCharCode(labelCount + 65);
                                        labelCount++;

                                        var marker = new LabeledMarker(ll, self.MarkerOptions);
                                        var mo = new MapOverlay(m.Id, marker, OverlayType.Marker, m.SubId);

                                        if (isCluster) {
                                            mo.ClusterInfo = { group: i, latlng: ll };

                                            /*
                                            GEvent.addListener(marker, "mouseover", function(ll) {
                                            self.onClusterMarkerMouseOver.call(self, crgmap, this, ll);
                                            });
                                            */
                                        }

                                        if (self.onMarkerMouseOver) {
                                            GEvent.addListener(marker, "mouseover", function(ll) {
                                                self.onMarkerMouseOver.call(self, crgmap, this, ll);
                                            });
                                        }

                                        self.addOverlay(crgmap, mo, false);

                                    }
                                }
                            }
                        }
                    }
                    else if (typeof (data.results) != "undefined" && data.results != null) {

                        for (var j = 0; j < data.results.length; j++) {
                            var m = data.results[j];

                            if (m.Lat != null && m.Lng != null) {
                                var ll = new GLatLng(m.Lat, m.Lng);

                                //							if (isCluster) {
                                //								var ll = new GLatLng(m.LatLng.Lat, m.LatLng.Lng);
                                //							}

                                if (self.AutoLabel)
                                    self.MarkerOptions.labelText = labelCount; //String.fromCharCode(labelCount + 65);
                                labelCount++;

                                var marker = new LabeledMarker(ll, self.MarkerOptions);
                                var mo = new MapOverlay(m.ID, marker, OverlayType.Marker);

                                if (self.onMarkerMouseOver) {
                                    GEvent.addListener(marker, "mouseover", function(ll) {
                                        self.onMarkerMouseOver.call(self, crgmap, this, ll);
                                    });
                                }

                                self.addOverlay(crgmap, mo, false);
                            }

                        }
                    }

                    self._addOverlaysToMap.call(self, crgmap);

                    if (callback)
                        callback.call(self, crgmap);

                    if (self.onLayerLoadComplete)
                        self.onLayerLoadComplete.call(self);

                }
                else {
                    if (self.onMaxResults)
                        self.onMaxResults.call(self, count);
                }

                crgmap.layersLoaded++;

                if (crgmap.layersLoaded >= crgmap.layersToLoad) {
                    crgmap._onLayersLoadComplete.call(crgmap, self);
                    crgmap.layersLoaded = 0;
                }
            }
        });
    },
    onClusterMarkerClick: function(crgmap, m, ll) {
    },
    isClustering: function(gmap) {
        return this.Cluster && this.MaxClusterZoom >= gmap.getZoom();
        //return this.MaxClusterZoom >= gmap.getZoom();
    },
    addOverlay: function(crgmap, mo, isCluster) {

        if (isCluster) {
            this.ClusterOverlays.push(mo);
        }
        else {
            var o = this.findOverlayById(mo);

            if (o == null) { //Create a new overlay
                this.Overlays.push(mo);
            }
        }
    },
    _addOverlaysToMap: function(crgmap) {
        for (var i = 0; i < this.Overlays.length; i++) {
            var mo = this.Overlays[i];
            var bounds = crgmap.GMap.getBounds();

            //Check if the google map bounds contains the center point of this overlay.  If it does, add it to the map
            if (!this.FilterByMapBounds || mo.getCenterLatLng() == null || bounds.containsLatLng(mo.getCenterLatLng())) {

                if (!mo.onMap) {
                    crgmap.GMap.addOverlay(mo.GOverlay);
                    /*
                    if (this.Cluster && mo.Type == OverlayType.Marker) {
                    this.MarkerClusterer.addMarker(mo.GOverlay);
                    }
                    else {
                    crgmap.GMap.addOverlay(mo.GOverlay);
                    }
                    */
                    mo.onMap = true;
                }

                mo.OutOfBounds = false;
            }
            else {
                mo.OutOfBounds = true;
            }
        }

        for (var i = 0; i < this.ClusterOverlays.length; i++) {
            var mo = this.ClusterOverlays[i];
            crgmap.GMap.addOverlay(mo.GOverlay);
        }
    },
    removeAllOverlays: function(crgmap) {
        for (var i = 0; i < this.Overlays.length; i++) {
            var mo = this.Overlays[i];
            /*
            if (this.Cluster)
            this.MarkerClusterer.removeMarker(mo.GOverlay);
            else
            */
            crgmap.GMap.removeOverlay(mo.GOverlay);
        }
        this.Overlays = new Array();

        for (var i = 0; i < this.ClusterOverlays.length; i++) {
            var mo = this.ClusterOverlays[i];
            crgmap.GMap.removeOverlay(mo.GOverlay);
        }
        this.ClusterOverlays = new Array();
    },
    removeClusteredOverlays: function(crgmap) {
        for (var i = 0; i < this.ClusterOverlays.length; i++) {
            var mo = this.ClusterOverlays[i];
            crgmap.GMap.removeOverlay(mo.GOverlay);
        }
        this.ClusterOverlays = new Array();

        if (this.isClustering(crgmap.GMap)) {
            this.removeOverlaysByType(crgmap, OverlayType.Marker);
        }
    },
    removeOverlaysByType: function(crgmap, otype) {
        var newOverlays = new Array();

        for (var i = 0; i < this.Overlays.length; i++) {
            var mo = this.Overlays[i];
            if (mo.Type == otype)
                crgmap.GMap.removeOverlay(mo.GOverlay);
            else
                newOverlays.push(mo);
        }
        this.Overlays = newOverlays;
    },
    removeOutOfBoundOverlays: function(crgmap) {
        this.Overlays.sort(this._sortOverlays);
        var endOfSlice = -1;
        for (var i = 0; i < this.Overlays.length; i++) {
            var mo = this.Overlays[i];
            if (mo.OutOfBounds) {
                /*
                if (this.Cluster)
                this.MarkerClusterer.removeMarker(mo.GOverlay);
                else
                */
                crgmap.GMap.removeOverlay(mo.GOverlay);

                if (endOfSlice == -1)
                    endOfSlice = i;
            }
        }

        if (endOfSlice > -1) {
            this.Overlays = this.Overlays.slice(0, endOfSlice);
            //this.MarkerClusterer.resetViewport()
        }
    },
    _sortOverlays: function(a, b) {
        if (a.OutOfBounds && b.OutOfBounds) {
            return 0;
        }
        else if (a.OutOfBounds && !b.OutOfBounds) {
            return 1;
        }
        else if (a.isClustered && b.isClustered) {
            return 0;
        }
        else if (a.isClustered && !b.isClustered) {
            return 1;
        }
        else
            return -1;
    },
    findOverlaysByClusterGroup: function(g) {
        var overlays = new Array();
        for (var i = 0; i < this.Overlays.length; i++) {
            var o = this.Overlays[i];

            if (o.ClusterInfo.group == g)
                overlays.push(o);
        }
        return overlays;
    },
    findOverlayById: function(mo) {
        for (var i = 0; i < this.Overlays.length; i++) {
            if (this.Overlays[i].Id == mo.Id && this.Overlays[i].SubId == mo.SubId) {
                return this.Overlays[i];
            }
        }
        return null;
    },
    findMapOverlayById: function(id) {
        for (var i = 0; i < this.Overlays.length; i++) {
            if (this.Overlays[i].Id == id) {
                return this.Overlays[i];
            }
        }
        return null;
    },
    findOverlay: function(gOverlay) {
        for (var i = 0; i < this.Overlays.length; i++) {
            if (this.Overlays[i].GOverlay == gOverlay) {
                return this.Overlays[i];
            }
        }
        return null;
    },
    hide: function() {
        /*
        hide all google overlays that are polygons, polylines, or markers 
        that aren't using the MarkerClusterer
        */
        for (var i = 0; i < this.Overlays.length; i++) {
            this.Overlays[i].GOverlay.hide();
            /*
            if (this.Overlays[i].Type != OverlayType.Marker || !this.Cluster)
            this.Overlays[i].GOverlay.hide();
            else
            this.Overlays[i].onMap = false; //going to clear all markers in cluster
            */
        }
        for (var i = 0; i < this.ClusterOverlays.length; i++) {
            this.ClusterOverlays[i].GOverlay.hide();
        }

        /*
        if (this.Cluster && this.MarkerClusterer)
        this.MarkerClusterer.clearMarkers();*/
    },
    show: function() {
        for (var i = 0; i < this.Overlays.length; i++) {
            this.Overlays[i].GOverlay.show();
            /*
            if (this.Overlays[i].Type != OverlayType.Marker || !this.Cluster)
            this.Overlays[i].GOverlay.show();			
            else if (this.Cluster && !this.Overlays[i].onMap) {
            this.MarkerClusterer.addMarker(this.Overlays[i].GOverlay);
            this.Overlays[i].onMap = true;
            }*/
        }
        for (var i = 0; i < this.ClusterOverlays.length; i++) {
            this.ClusterOverlays[i].GOverlay.show();
        }
    },
    onLayerLoadComplete: null,
    onMaxResults: function(total) {
        alert('Please zoom in or refine your search: ' + total);
    },
    onClick: null,
    onMarkerMouseOver: null,
    onMarkerMouseOut: null//,
    //onMaxResults: null
}
TileLayer = function(name, url, opts) {
    this.Name = name;
    this.TileUrl = url;

    if (typeof (opts) == "undefined")
        opts = {};

    this.Group = opts.group;

    if (typeof (opts.visible) == "undefined")
        opts.visible = true;
    this.Visible = opts.visible;

    if (typeof (opts.isPng) == "undefined")
        opts.isPng = true;
    this.IsPng = opts.ispng;

    this.UrlExtension = opts.urlExt || ".ashx";
    this.Opacity = opts.opacity || 1.0;
    this.MinZoom = opts.minZoom || 0; //0 is highest, 19 is closer
    this.MaxZoom = opts.maxZoom || 19; //0 is highest, 19 is closer
    this.ZPriority = opts.zPriority || 1;

    this.enabled = true;
    this.GOverlay = null;
    this.type = LayerType.Tile;
}
TileLayer.prototype = {
    bind: function(crgmap) {
        var self = this;
        var tileLayer = new GTileLayer(new GCopyrightCollection(''), this.MinZoom, this.MaxZoom);
        tileLayer.getTileUrl = function(p, z) {
            return self.TileUrl + crgmap._tileToQuadKey(p.x, p.y, z) + self.UrlExtension;
        };
        tileLayer.getCopyright = function(bounds, zoom) { return "CRG, 2009"; };
        tileLayer.isPng = function() { return self.IsPng; };
        tileLayer.getOpacity = function() { return self.Opacity; }

        var layer = new GTileLayerOverlay(tileLayer, { zPriority: this.ZPriority });
        crgmap.GMap.addOverlay(layer);

        if (!this.Visible)
            layer.hide();

        this.GOverlay = layer;
    },
    hide: function() {
        this.GOverlay.hide();
    },
    show: function() {
        this.GOverlay.show();
    },
    onClick: null//,
}
GeoXmlLayer = function(name, url, opts) {
    this.Name = name;
    this.GeoXmlUrl = url;

    if (typeof (opts) == "undefined")
        opts = {};

    this.Group = opts.group;

    if (typeof (opts.visible) == "undefined")
        opts.visible = true;
    this.Visible = opts.visible;

    //this.MinZoom = opts.minZoom || 0; //0 is highest, 19 is closer
    //this.MaxZoom = opts.maxZoom || 19; //0 is highest, 19 is closer

    this.enabled = true;
    this.GOverlay = null;
    this.type = LayerType.GeoXml;
}
GeoXmlLayer.prototype = {
    bind: function(crgmap) {
        if (crgmap.onLayersLoadStart && crgmap.layersLoaded == 0) {
            crgmap.onLayersLoadStart.call(this);
        }

        var geoXml = new GGeoXml(this.GeoXmlUrl);
        crgmap.GMap.addOverlay(geoXml);
        if (!this.Visible) {
            geoXml.hide();
        }
        this.GOverlay = geoXml;

        crgmap.layersLoaded++;
        if (crgmap.layersLoaded >= crgmap.layersToLoad) {
            crgmap._onLayersLoadComplete.call(crgmap, self);
            crgmap.layersLoaded = 0;
        }
    },
    hide: function() {
        this.GOverlay.hide();
    },
    show: function() {
        this.GOverlay.show();
    },
    onClick: null//,
}
CRGGoogleMap = function(map, opts) {
    this.GMap = map
    this.MapLayers = new Array();
    this.TileLayers = new Array();
    this.GeoXmlLayers = new Array();
    //this.MapLayerBounds = new GLatLngBounds();
    this.layersLoaded = 0;
    this.layersToLoad = opts.layersToLoad || 0;
    this.layersCentered = false;

    if (typeof (opts) == "undefined")
        opts = {};

    this.MaxGeoPerLayer = opts.maxGeo || GEOTHRESHOLD;
    this.MaxInitZoom = opts.maxInitZoom || MAX_INITIAL_ZOOM;
    this.MinClusterSize = opts.minClusterSize || MIN_CLUSTER_SIZE;

    this.refresh = true;
    //this.MarkerClusterer = new MarkerClusterer(map);

}
CRGGoogleMap.prototype = {
    initialize: function() {
        var self = this;
        GEvent.addListener(this.GMap, "click", function(overlay, latlng, overlaylatlng) {
            self._onClick.call(self, overlay, latlng, overlaylatlng);
        });
        GEvent.addListener(this.GMap, "mousemove", function(latlng) {
            self.onClusterMouseMove.call(self, latlng);
        });
        GEvent.addListener(this.GMap, "moveend", function() {
            if (self.refresh)
                self.refreshLayers();
        });
        GEvent.addListener(this.GMap, "zoomend", function(oldLevel, newLevel) {
            if (self.onZoomEnd)
                self.onZoomEnd.call(self, oldLevel, newLevel);
        });
    },
    dispose: function() {
        //Add custom dispose actions here
    },
    refreshLayers: function() {
        //don't clear all overlays
        //this.GMap.clearOverlays();

        this.layersToLoad = this._getNumOfVisibleLayersForRefresh();

        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];

            //If map is set to visible and map is set to filter by bounds
            if (ml.Visible && ml.enabled && ml.FilterByMapBounds) {
                //ml.resetDirtyFlag();
                ml.show();
                ml.bind(this, ml.removeOutOfBoundOverlays);
                //ml.bind(this);
            }
            //If map is not enabled or not visible
            else if (!ml.Visible || !ml.enabled) {
                ml.hide();
            }
        }
    },
    //Reset open clusters that the mouse has moved out of bounds on.
    onClusterMouseMove: function(latlng) {
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];

            for (var j = 0; j < ml.Overlays.length; j++) {
                var mo = ml.Overlays[j];
                if (typeof (mo.ClusterInfo.bounds) != "undefined") {
                    if (!mo.ClusterInfo.bounds.containsLatLng(latlng)) {
                        mo.GOverlay.setLatLng(mo.ClusterInfo.latlng);
                        this.GMap.removeOverlay(mo.ClusterInfo.line);
                    }
                }
            }
        }
    },
    _getNumOfVisibleLayersForRefresh: function() {
        var count = 0;
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];
            this._setLayerEnabled(ml);

            if (ml.Visible && ml.enabled && ml.FilterByMapBounds) {
                count++;
            }
        }
        return count;
    },
    addLayer: function(layer) {
        //push this layer into the crg map object

        if (layer.type == LayerType.Overlay) {
            this.MapLayers.push(layer);

            this._setLayerEnabled(layer);

            if (layer.Visible && layer.enabled) {
                this.layersToLoad++;

                layer.bind(this);
            }
        }
        else if (layer.type == LayerType.Tile) {
            this.TileLayers.push(layer);
            layer.bind(this);
        }
        else if (layer.type == LayerType.GeoXml) {
            this.GeoXmlLayers.push(layer);
            layer.bind(this);
        }
    },
    _hideLayer: function(layer) {
        layer.Visible = false;
        if (layer.enabled) {
            layer.hide();
        }
    },
    _showLayer: function(layer) {
        layer.Visible = true;
        if (layer.enabled) {
            layer.show();

            if (layer.type == LayerType.Overlay) {
                this.layersToLoad = 1;
                layer.bind(this, layer.removeOutOfBoundOverlays)
            }
        }
    },
    hideLayer: function(name) {
        var ml = this.findLayer(name);
        if (ml != null) {
            this._hideLayer(ml);
        }
    },
    showLayer: function(name) {
        var ml = this.findLayer(name);
        if (ml != null) {
            this._showLayer(ml);
        }
    },
    toggleLayer: function(name) {
        var ml = this.findLayer(name);

        if (ml != null) {
            if (ml.Visible) {
                this._hideLayer(ml);
            }
            else {
                this._showLayer(ml);
            }
        }
    },
    hideLayerGroup: function(gname) {
        var layers = this.findLayersByGroup(gname);
        for (var i = 0; i < layers.length; i++) {
            var ml = layers[i];
            this._hideLayer(ml);
        }
    },
    showLayerGroup: function(gname) {
        var layers = this.findLayersByGroup(gname);
        for (var i = 0; i < layers.length; i++) {
            var ml = layers[i];
            this._showLayer(ml);
        }
    },
    toggleLayerGroup: function(gname) {
        var layers = this.findLayersByGroup(gname);
        for (var i = 0; i < layers.length; i++) {
            var ml = layers[i];
            if (ml.Visible) {
                this._hideLayer(ml);
            }
            else {
                this._showLayer(ml);
            }
        }
    },
    //Returns whether the layer is visible (visible = true and enabled = true)
    //parameters: name = layer name
    //return: bool
    isLayerVisible: function(name) {
        var ml = this.findLayer(name);
        return ml.Visible && ml.enabled;
    },
    //Returns whether the layer is enabled
    //parameters: name = layer name
    //return: bool
    isLayerEnabled: function(name) {
        var ml = this.findLayer(name);

        if (ml != null)
            return this._isLayerEnabled(ml);
        else
            return false;
    },
    _isLayerEnabled: function(layer) {
        this._setLayerEnabled(layer);
        return layer.enabled;
    },
    isLayerGroupEnabled: function(gname) {
        var enabled = true;

        var layers = this.findLayersByGroup(gname);
        for (var i = 0; i < layers.length; i++) {
            var ml = layers[i];
            var e = this._isLayerEnabled(ml);
            enabled = enabled && e;
            if (!enabled) {
                break;
            }
        }

        return enabled;
    },
    _setLayerEnabled: function(layer) {
        if (this.GMap.getZoom() < layer.MinZoom)
            layer.enabled = false;
        else
            layer.enabled = true;
    },
    findLayer: function(name) {
        var ml = null;
        for (var i = 0; i < this.MapLayers.length; i++) {
            if (this.MapLayers[i].Name == name) {
                ml = this.MapLayers[i];
                break;
            }
        }
        for (var i = 0; i < this.TileLayers.length; i++) {
            if (this.TileLayers[i].Name == name) {
                ml = this.TileLayers[i];
                break;
            }
        }
        for (var i = 0; i < this.GeoXmlLayers.length; i++) {
            if (this.GeoXmlLayers[i].Name == name) {
                ml = this.GeoXmlLayers[i];
                break;
            }
        }
        return ml;
    },
    findLayerByOverlay: function(gOverlay) {
        var ml = null;
        for (var i = 0; i < this.MapLayers.length; i++) {
            ml = this.MapLayers[i];
            var mo = ml.findOverlay(gOverlay);
            if (mo != null) {
                break;
            }
            else {
                ml = null;
            }
        }
        return ml;
    },
    findLayersByGroup: function(group) {
        var layers = new Array();
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];
            if (ml.Group == group) {
                layers.push(ml);
            }
        }

        for (var i = 0; i < this.TileLayers.length; i++) {
            var ml = this.TileLayers[i];
            if (ml.Group == group) {
                layers.push(ml);
            }
        }

        for (var i = 0; i < this.GeoXmlLayers.length; i++) {
            var ml = this.GeoXmlLayers[i];
            if (ml.Group == group) {
                layers.push(ml);
            }
        }

        return layers;
    },
    getVisibleTileLayers: function() {
        var layers = new Array();
        for (var i = 0; i < this.TileLayers.length; i++) {
            var ml = this.TileLayers[i];
            if (ml.Visible) {
                layers.push(ml);
            }
        }
        return layers;
    },
    findOverlay: function(gOverlay) {
        var mOverlay = null;
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];
            var mo = ml.findOverlay(gOverlay);
            if (mo != null) {
                mOverlay = mo;
                break;
            }
        }
        return mOverlay;
    },
    findOverlaysById: function(id) {
        var mos = new Array();
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];

            for (var j = 0; j < ml.Overlays.length; j++) {
                if (ml.Overlays[j].Id == id) {
                    mos.push(ml.Overlays[j]);
                }
            }
        }
        return mos;
    },
    isClusterOverlay: function(go) {
        var isco = false;
        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];

            for (var j = 0; j < ml.ClusterOverlays.length; j++) {
                if (ml.ClusterOverlays[j].GOverlay == go) {
                    isco = true;
                    return isco;
                }
            }
        }
        return isco;
    },
    panToOverlay: function(mo, zoom) {
        this.GMap.panTo(mo.getCenterLatLng());
        if (typeof (zoom) != "undefined")
            this.GMap.setZoom(zoom);
    },
    panToOverlayById: function(id) {
        var overlays = this.findOverlaysById(id);
        if (overlays.length > 0) {
            this.GMap.panTo(overlays.pop().getCenterLatLng());
        }
    },
    //Returns a list of all the layer names that have been added to the map
    getLayerNames: function() {
        var names = new Array();
        for (var i = 0; i < this.MapLayers.length; i++) {
            names.push(this.MapLayers[i].Name);
        }
        for (var i = 0; i < this.TileLayers.length; i++) {
            names.push(this.TileLayers[i].Name);
        }
        for (var i = 0; i < this.GeoXmlLayers.length; i++) {
            names.push(this.GeoXmlLayers[i].Name);
        }
        return names;
    },
    getBoundingBox: function() {
        var mbounds = this.GMap.getBounds();
        var swLatLng = mbounds.getSouthWest();
        var neLatLng = mbounds.getNorthEast();
        var bb = new BoundingBox(swLatLng.lat(), swLatLng.lng(), neLatLng.lat(), neLatLng.lng());
        return bb;
    },
    centerByLayerBounds: function() {
        this.layersCentered = true;
        var bounds = new GLatLngBounds();
        var override = false;

        for (var i = 0; i < this.MapLayers.length; i++) {
            var ml = this.MapLayers[i];
            if (ml.Center) {
                for (var j = 0; j < ml.Overlays.length; j++) {
                    if (ml.Overlays[j].Type == OverlayType.Marker) {
                        bounds.extend(ml.Overlays[j].GOverlay.getLatLng());
                        override = true;
                    }
                }
                for (var j = 0; j < ml.ClusterOverlays.length; j++) {
                    if (ml.ClusterOverlays[j].Type == OverlayType.Marker) {
                        bounds.extend(ml.ClusterOverlays[j].GOverlay.getLatLng());
                        override = true;
                    }
                }
            }
        }



        //Override the current zoom and center since there are markers on the map.
        if (override) {
            //make sure to not refresh the layers until both zoom and center is done.
            this.refresh = false;

            var newZoom = this.GMap.getBoundsZoomLevel(bounds);
            if (newZoom > this.MaxInitZoom)
                newZoom = this.MaxInitZoom;

            this.GMap.setZoom(newZoom);

            this.refresh = true;
            this.GMap.setCenter(bounds.getCenter());
        }
    },
    _markerClick: function(mo) {
        $.ajax({
            type: "GET",
            url: "/MapService.svc/GetMarkerInfoWindow",
            data: { 'id': mo.Id },
            dataType: "json",
            //processData: false,
            //contentType: "application/json; charset=utf-8",
            success: function(data) {
                mo.GOverlay.openInfoWindow(data.d);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                alert("request:" + XMLHttpRequest + ", status: " + textStatus + ", error: " + errorThrown);
                for (var property in XMLHttpRequest) {
                    //window.alert(property + ": " + XMLHttpRequest[property]);
                }
            }
        });
    },
    _onClick: function(o, ll, oll) {
        //Marker click
        if (o != null && typeof (o.getLatLng) != "undefined") {
            var crgmap = this;

            if (this.isClusterOverlay(o)) {
                crgmap.GMap.setCenter(o.getLatLng(), crgmap.GMap.getZoom() + 2);
                //crgmap.GMap.zoomIn(o.getLatLng());
            }
            else {
                var ml = this.findLayerByOverlay(o);
                if (ml) {
                    if (ml.onClick) {
                        ml.onClick.call(crgmap, o, ll, oll);
                    }
                    else {
                        this._markerClick(this.findOverlay(o));
                    }
                }
            }
        }
        else {
            //Check all visible tile layers and see if they have an onclick function
            var tilelayers = this.getVisibleTileLayers();
            for (var i = 0; i < tilelayers.length; i++) {
                var tl = tilelayers[i];
                if (tl.onClick) {
                    tl.onClick.call(crgmap, o, ll, oll);
                }
            }
        }
    },
    onLayersLoadStart: function(ml) {
    },
    _onLayersLoadComplete: function(ml) {
        if (!this.layersCentered)
            this.centerByLayerBounds();

        if (this.onLayersLoadComplete)
            this.onLayersLoadComplete.call(ml);
    },
    onLayersLoadComplete: null,
    onZoomEnd: null,
    _tileToQuadKey: function(x, y, zoom) {
        var quad = "";
        for (var i = zoom; i > 0; i--) {
            var mask = 1 << (i - 1);
            var cell = 0;
            if ((x & mask) != 0)
                cell++;
            if ((y & mask) != 0)
                cell += 2;
            quad += cell;
        }
        return quad;
    }
}
