﻿/*
CRG Google Map
Developed by: Michael Tecson
Date: 2009.09.01
Updated: 2009.10.26
Version: 1.2
*/

/// <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 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.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);

		/*
		if (this.MarkerClusterer == null) {
		this.MarkerClusterer = new MarkerClusterer(gmap, null, this.ClusterMarkerOptions);
		}
		*/
		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 = MIN_CLUSTER_SIZE;
		}

		/*
		if (this._isCustomClustering(gmap)) {
		this.removeAllOverlays(crgmap);
		//this.Param.zoomLevel = gmap.getZoom();
		}
		else {
		this.removeCustomClusteredOverlays(crgmap);
		//this.Param.zoomLevel = null;
		}
		*/

		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({
			type: "GET",
			url: this.WebServiceUrl,
			data: this.Param,
			dataType: "json",
			cache: false,
			//processData: false,
			contentType: "application/json; charset=utf-8",
			success: function(data, textStatus) {

				var o = data.d;

				if (o.Count <= crgmap.MaxGeoPerLayer) {

					//Clear all custom clustered markers.
					self.removeClusteredOverlays(crgmap);

					if (o.ClusterMarkers != null) {

						//var newId = 0;
						//var oldId = 0;
						var labelCount = 0;

						for (var i = 0; i < o.ClusterMarkers.length; i++) {
							var cm = o.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 {
								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 = 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);
										});
									}

									self.addOverlay(crgmap, mo, false);

								}
							}


							/*
							var marker = null;
							var mIsClustered = o.Markers[i].IsClustered;

							//if clustering is allowed and this marker is clustered, label it
							if (self._isCustomClustering(gmap) && mIsClustered) {
							self.CustomClusterMarkerOptions.labelText = o.Markers[i].ClusterCount;
							var marker = new LabeledMarker(ll, self.CustomClusterMarkerOptions);
							}
							else {
							var marker = new LabeledMarker(ll, self.MarkerOptions);
							}

							var mOverlay = new MapOverlay(o.Markers[i].Id, marker, OverlayType.Marker, o.Markers[i].SubId);

							if (self._isCustomClustering(gmap) && mIsClustered) {
							mOverlay.isClustered = true;
							mOverlay.clusterCount = o.Markers[i].ClusterCount;
							}
							self.addOverlay(crgmap, 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].Latitude, pl.LatLngs[j].Longitude);
					//								latlngs.push(ll);
					//							}
					var strokeColor = GetRandomHtmlColor();
					//var polyline = new GPolyline(latlngs, strokeColor, 2, 0.5);
					//var polyline = new GPolyline.fromEncoded(strokeColor, 2, 0.5, pl.EncodedLatLngs, ENCODER_zoomFactor, pl.EncodedLevels, ENCODER_numLevels);

							var encodedPolyline = new GPolyline.fromEncoded({
					color: strokeColor,
					weight: 2,
					opacity: 0.8,
					points: pl.EncodedLatLngs,
					levels: pl.EncodedLevels,
					zoomFactor: ENCODER_zoomFactor,
					numLevels: ENCODER_numLevels

							});
					var mOverlay = new MapOverlay(pl.Id, encodedPolyline, OverlayType.Polyline, pl.SubId);
					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);
			}
		});
	},
	onClusterMarkerMouseOver: function(crgmap, m, ll) {
		var mo = crgmap.findOverlay(m);
		var overlays = this.findOverlaysByClusterGroup(mo.ClusterInfo.group);
		var mapZoom = crgmap.GMap.getZoom();

		if (mapZoom >= MIN_CLUSTER_EXPAND_ZOOM) {
			var shift = CLUSTER_SHIFT;
			var bshift = shift + 0.0003;
			var step = overlays.length;
			var tick = 1;
			var deg = (360 / (step));

			var bounds = new GLatLngBounds();
			for (var i = 0; i < overlays.length; i++) {
				var mov = overlays[i];
				var o = mov.GOverlay;
				var oLatLng = mov.ClusterInfo.latlng;

				var delta_lat = (shift + shift * (Math.pow(2, (17 - mapZoom)) - 1)) * Math.cos(((deg * tick) * Math.PI) / 180);
				var delta_lng = (shift + shift * (Math.pow(2, (17 - mapZoom)) - 1)) * Math.sin(((deg * tick) * Math.PI) / 180);
				var new_lat = oLatLng.lat() + delta_lat;
				var new_lng = oLatLng.lng() + delta_lng;

				o.setLatLng(new GLatLng(new_lat, new_lng));

				var bdelta_lat = (bshift + bshift * (Math.pow(2, (17 - mapZoom)) - 1)) * Math.cos(((deg * tick) * Math.PI) / 180);
				var bdelta_lng = (bshift + bshift * (Math.pow(2, (17 - mapZoom)) - 1)) * Math.sin(((deg * tick) * Math.PI) / 180);
				var bnew_lat = oLatLng.lat() + bdelta_lat;
				var bnew_lng = oLatLng.lng() + bdelta_lng;

				if (typeof (mov.ClusterInfo.line) != "undefined") {
					crgmap.GMap.removeOverlay(mov.ClusterInfo.line);
				}
				mov.ClusterInfo.line = new GPolyline([oLatLng, o.getLatLng()], '#000000', 3, 0.8);
				crgmap.GMap.addOverlay(mov.ClusterInfo.line);

				bounds.extend(new GLatLng(bnew_lat, bnew_lng));
				tick++;
			}

			var proj = crgmap.GMap.getCurrentMapType().getProjection();
			var swPoint = proj.fromLatLngToPixel(bounds.getSouthWest(), mapZoom)
			var nePoint = proj.fromLatLngToPixel(bounds.getNorthEast(), mapZoom)

			if (swPoint.x == nePoint.x || swPoint.y == nePoint.y) {
				var new_sw = new GPoint(swPoint.x, swPoint.y)
				var new_ne = new GPoint(nePoint.x, nePoint.y)

				if (swPoint.x == nePoint.x) {
					new_sw.x -= 10;
					new_ne.x += 10;
				}
				if (swPoint.y == nePoint.y) {
					new_sw.y -= 10;
					new_ne.y += 10;
				}

				var new_swll = proj.fromPixelToLatLng(new_sw, mapZoom);
				var new_nell = proj.fromPixelToLatLng(new_ne, mapZoom);

				bounds = new GLatLngBounds(new_swll, new_nell);
			}

			//getSouthWest() 	GLatLng 	Returns the point at the south-west corner of the rectangle.
			//getNorthEast() 
			//GLatLngBounds(sw?:GLatLng, ne?:GLatLng)

			//fromLatLngToPixel(latlng:GLatLng, zoom:Number) 
			//fromPixelToLatLng(pixel:GPoint, zoom:Number, unbounded?:Boolean) 

			for (var i = 0; i < overlays.length; i++) {
				var o = overlays[i];
				o.ClusterInfo.bounds = bounds;
			}
		}
	},
	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);
		}
	},
	//	_addOverlaysWithOffset: function(overlays) {
	//		var clustered = new Array();
	//		for (var i = 0; i < overlays.length; i++) {
	//			var mo = overlays[i];
	//			var cluster = new Array();
	//		}
	//	},
	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;
	},
	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//,
	//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.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);
		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, "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);
		}
	},
	_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);
			}
		}

		for (var i = 0; i < this.TileLayers.length; i++) {
			var ml = this.TileLayers[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;
	},
	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);
		}
		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: "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.onClick) {
					ml.onClick.call(crgmap, o, ll, oll);
				}
				else {
					this._markerClick(this.findOverlay(o));
				}
			}
		}
	},
	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;
	}
}