﻿/*
CRG Google Map
Developed by: Michael Tecson
Date: 2009.09.01
Updated: 2009.10.15
Version: 1.1
*/

/// <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 };
var GEOTHRESHOLD = 250; //max number of markers/geos per layer
var MAX_INITIAL_ZOOM = 19; //max number of markers/geos per layer
var ENCODER_zoomFactor = 18;
var ENCODER_numLevels = 3;

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, c) {
    this.Id = id;
    this.SubId = subid || 0;
    this.C = c || 0;
    this.GOverlay = gOverlay;
    this.Type = type || OverlayType.Marker;
    this.OutOfBounds = false;
    this.onMap = false;
}
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.MinZoom = opts.minZoom || 0; //0 is highest, 19 is closer

    if (typeof (opts.strokeColor) == "undefined") {
        opts.strokeColor = GetRandomHtmlColor();
    }
    this.StrokeColor = opts.strokeColor;

    if (typeof (opts.strokeWeight) == "undefined") {
        opts.strokeWeight = 1;
    }
    this.StrokeWeight = opts.strokeWeight;

    if (typeof (opts.strokeOpacity) == "undefined") {
        opts.strokeOpacity = 1;
    }
    this.StrokeOpacity = opts.strokeOpacity;

    this.MarkerClusterer = null;
    this.MarkerOptions = {};
    this.ClusterMarkerOptions = {};
    this.Overlays = 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);

        if (this.MarkerClusterer == null) {
            this.MarkerClusterer = new MarkerClusterer(gmap, null, this.ClusterMarkerOptions);
        }

        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);
        }

        $.ajax({
            url: this.WebServiceUrl,
            dataType: 'jsonp',
            data: this.Param,
            jsonp: 'method',
            //jsonpCallback: 'method',
            success: function(data, textStatus) {

                //        $.getJSON(this.WebServiceUrl, this.Param,
                //			function(data, textStatus) {

                var o = data.d;

                if (o.Count <= crgmap.MaxGeoPerLayer) {
                    if (o.Markers != null) {

                        var newId = 0;
                        var oldId = 0;
                        var labelCount = 0;
                        for (var i = 0; i < o.Markers.length; i++) {
                            var ll = new GLatLng(o.Markers[i].LatLng.Latitude, o.Markers[i].LatLng.Longitude);

                            newId = o.Markers[i].Id;
                            if (newId != oldId) {
                                if (self.AutoLabel)
                                    self.MarkerOptions.labelText = String.fromCharCode(labelCount + 65);

                                oldId = newId;
                                labelCount++;
                            }

                            var marker = new LabeledMarker(ll, self.MarkerOptions);

                            //gmap.addOverlay(marker);
                            //crgmap.MapLayerBounds.extend(ll);

                            var mOverlay = new MapOverlay(o.Markers[i].Id, marker, OverlayType.Marker, o.Markers[i].SubId);
                            self.addOverlay(crgmap, mOverlay);
                            //self.Overlays.push(mOverlay);
                        }
                    }

                    if (o.Polylines != null) {
                        for (var i = 0; i < o.Polylines.length; i++) {
                            var pl = o.Polylines[i];
                            var latlngs = new Array();
                            for (var j = 0; j < pl.LatLngs.length; j++) {
                                var ll = new GLatLng(pl.LatLngs[j].Lat, pl.LatLngs[j].Lng);
                                latlngs.push(ll);
                            }
                            var strokeColor = self.StrokeColor;
                            var strokeWeight = self.StrokeWeight;
                            var strokeOpacity = self.StrokeOpacity;
                            var polyline = new GPolyline(latlngs, strokeColor, strokeWeight, strokeOpacity);
                            if (self.onMouseOver) {
                                GEvent.addListener(polyline, "mouseover", function(ll) {
                                    self.onMouseOver.call(self, crgmap, this, ll);
                                });
                            }
                            if (self.onMouseOut) {
                                GEvent.addListener(polyline, "mouseout", function(ll) {
                                    self.onMouseOut.call(self, crgmap, this, ll);
                                });
                            }
                            /*
                            var encodedPolyline = new GPolyline.fromEncoded({
                            color: strokeColor,
                            weight: strokeWeight,
                            opacity: strokeOpacity,
                            points: pl.EncodedLatLngs,
                            levels: pl.EncodedLevels,
                            zoomFactor: ENCODER_zoomFactor,
                            numLevels: ENCODER_numLevels
                            });
                            */
                            //var mOverlay = new MapOverlay(pl.Id, encodedPolyline, OverlayType.Polyline, pl.SubId);
                            var mOverlay = new MapOverlay(pl.Id, polyline, OverlayType.Polyline, pl.SubId, pl.C);
                            self.addOverlay(crgmap, mOverlay);
                        }
                    }

                    if (o.Polygons != null) {
                        for (var i = 0; i < o.Polygons.length; i++) {
                            var pg = o.Polygons[i];
                            var latlngs = new Array();
                            for (var j = 0; j < pg.LatLngs.length; j++) {
                                var ll = new GLatLng(pg.LatLngs[j].Latitude, pg.LatLngs[j].Longitude);
                                latlngs.push(ll);
                            }
                            var strokeColor = GetRandomHtmlColor();
                            var fillColor = GetRandomHtmlColor();

                            var polygon = new GPolygon(latlngs, strokeColor, 2, 0.5, fillColor, 0.5);
                            //GPolygon.fromEncoded(polylines:encoded polylines[], fill?:Boolean, color?:String, opacity?:Number, outline?:Boolean) 
                            var encodedPolygon = new GPolygon.fromEncoded(
							{
							    polylines:
								[{
								    color: strokeColor,
								    weight: 2,
								    opacity: 0.5,
								    points: pg.EncodedLatLngs,
								    levels: pg.EncodedLevels,
								    zoomFactor: ENCODER_zoomFactor,
								    numLevels: ENCODER_numLevels
}],
							    fill: true,
							    color: fillColor,
							    opacity: 0.5,
							    outline: true
							});

                            var mOverlay = new MapOverlay(pg.Id, polygon, OverlayType.Polygon, pg.SubId);
                            self.addOverlay(crgmap, mOverlay);
                        }
                    }

                    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, o.Count);
                }

                crgmap.layersLoaded++;

                if (crgmap.layersLoaded == crgmap.layersToLoad) {
                    crgmap._onLayersLoadComplete.call(crgmap, self);
                    crgmap.layersLoaded = 0;
                }

                //do this after you push ALL layers
                //gmap.setZoom(map.getBoundsZoomLevel(crgmap.MapLayerBounds));
                //gmap.setCenter(crgmap.MapLayerBounds.getCenter());
                //				},
                //				error: function(XMLHttpRequest, textStatus, errorThrown) {
                //					crgmap.layersLoaded++;
                //					alert('load layer: ' + textStatus);
                //				}
            }
        });
    },
    addOverlay: function(crgmap, mo) {
        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) {
                    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;
            }
        }
    },
    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
            return -1;
    },
    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
            && this.Overlays[i].C == mo.C) {
                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++) {
            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
        }

        if (this.Cluster && this.MarkerClusterer)
            this.MarkerClusterer.clearMarkers();
    },
    show: function() {
        for (var i = 0; i < this.Overlays.length; i++) {
            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;
            }
        }
    },
    onLayerLoadComplete: null,
    onMaxResults: function(total) {
        alert('Please zoom in or refine your search: ' + total);
    },
    onClick: null,
    onMouseOver: null,
    onMouseOut: null
    //onMaxResults: null
}
TileLayer = function(name, url, opts) {
    this.Name = name;
    this.TileUrl = url;

    if (typeof (opts) == "undefined")
        opts = {};

    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.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.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) + ".ashx";
        };
        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);
        crgmap.GMap.addOverlay(layer);

        if (!this.Visible)
            layer.hide();

        this.GOverlay = layer;
    },
    hide: function() {
        this.GOverlay.hide();
    },
    show: function() {
        this.GOverlay.show();
    }
}
CRGGoogleMap = function(map, opts) {
    this.GMap = map
    this.MapLayers = new Array();
    this.TileLayers = new Array();
    //this.MapLayerBounds = new GLatLngBounds();
    this.layersLoaded = 0;
    this.layersToLoad = 0;
    this.layersCentered = false;

    if (typeof (opts) == "undefined")
        opts = {};

    this.MaxGeoPerLayer = opts.maxGeo || GEOTHRESHOLD;
    this.MaxInitZoom = opts.maxInitZoom || MAX_INITIAL_ZOOM;

    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, "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();
            }
        }
    },
    _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);
        }
    },
    _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;
            }
        }
        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);
            }
        }
        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;
    },
    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);
        }
        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;
                    }
                }
            }
        }

        //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: "POST",
            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) {
        if (o != null) {
            //Marker click
            if (typeof (o.getLatLng) != "undefined") {
                var crgmap = this;
                var ml = this.findLayerByOverlay(o);
                if (ml && ml.onClick) {
                    ml.onClick.call(crgmap, o, ll, oll);
                }
                else {
                    this._markerClick(this.findOverlay(o));
                }
            }
            //Polyline click
            else {
                var crgmap = this;
                var ml = this.findLayerByOverlay(o);
                if (ml && ml.onClick) {
                    ml.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;
    }
}
