 



(function(){
	
	if(typeof tbelt ==="undefined") window["tbelt"]={};
	var TBELT = tbelt;
	
	var GMAPS = TBELT.gmaps = {
			
		getDistance:function(latlng1, latlng2){
			/*
			 * Vincenty Inverse Solution of Geodesics on the Ellipsoid (c) Chris Veness 2002-2009 
			 * Calculate geodesic distance (in m) between two points specified by latitude/longitude 
			 * (in numeric degrees) using Vincenty inverse formula for ellipsoids
			 */
			var lat1 = latlng1.lat(),
				lng1 = latlng1.lng(),
				lat2 = latlng2.lat(),
				lng2 = latlng2.lng();
				
			var a = 6378137, b = 6356752.3142,  f = 1/298.257223563;  // WGS-84 ellipsiod
			var L = (lng2-lng1).toRad();
			var U1 = Math.atan((1-f) * Math.tan(lat1.toRad()));
			var U2 = Math.atan((1-f) * Math.tan(lat2.toRad()));
			var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
			var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

			var lambda = L, lambdaP, iterLimit = 100;
			do {
			var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
			var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + 
			  (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
			if (sinSigma==0) return 0;  // co-incident points
			var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
			var sigma = Math.atan2(sinSigma, cosSigma);
			var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
			var cosSqAlpha = 1 - sinAlpha*sinAlpha;
			var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
			if (isNaN(cos2SigmaM)) cos2SigmaM = 0;  // equatorial line: cosSqAlpha=0 (§6)
			var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
			lambdaP = lambda;
			lambda = L + (1-C) * f * sinAlpha *
			  (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
			} while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0);

			if (iterLimit==0) return NaN  // formula failed to converge

			var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
			var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
			var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
			var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
			B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
			var s = b*A*(sigma-deltaSigma);

			s = s.toFixed(3); // round to 1mm precision
			return s;
		},
		
		
		randomLatLng:function(bounds){
			return new google.maps.LatLng(tbelt.num.random(bounds.n, bounds.s), tbelt.num.random(bounds.w, bounds.e));			
		},
		
		
		//match the bounds from one ratio to another
		/**
		 * Matches bounds1's ratio to bounds2, extendind from any corner, or the center.
		 * @method boundsRatioMatch
		 * @param bounds1 {google.maps.LatLngBounds} The bounds to be extended.
		 * @param bounds2 {google.maps.LatLngBounds} The bounds used as the ratio model.
		 * @param extendFrom {string} "ne", "nw", "se", "sw", "center"
		 */			
		boundsRatioMatch:function(bounds1, bounds2, extendFrom){
		
		
		}
	};
		
		
	
	
	
	
	(function(){
			
		function OverlayView(map){
			this.setMap(map);
		}
		OverlayView.prototype = new google.maps.OverlayView();
		OverlayView.prototype.onAdd = function(){}
		OverlayView.prototype.onRemove = function(){}
		OverlayView.prototype.draw = function(){
			if(!this.ready){
				this.ready=true;
				google.maps.event.trigger(this, "ready");
			}
		}
		
		var overlayView = null;
		
		google.maps.Map.prototype.getOverlayView=function(){
			if(overlayView==null) overlayView = new OverlayView(this);
			return overlayView;
		}
	
	})();
	
	google.maps.LatLngBounds.prototype.toNSEW=function(){
		var sw = this.getSouthWest(),
			ne = this.getNorthEast();
		return {n:ne.lat(), s:sw.lat(), e:ne.lng(), w:sw.lng()};	 
	}
	
	
	google.maps.LatLngBounds.prototype.toDivPixels=function(projection){
		var sw = this.getSouthWest(),
			bl = projection.fromLatLngToDivPixel(sw),
			ne = this.getNorthEast(),
			tr = projection.fromLatLngToDivPixel(ne),
			rect = {
				topRight:tr,
				bottomLeft:bl,
				x:bl.x,
				y:tr.y, 
				height:bl.y-tr.y,
				width:tr.x-bl.x
			};
			
		return rect;	 
	}
	
	

})();(function(){

	var Polyrect = tbelt.gmaps.Polyrect = function(options){
		var I = this;
		
		var nsew = options.path.toNSEW(),
			path = [
				new google.maps.LatLng(nsew.n, nsew.w),
				new google.maps.LatLng(nsew.s, nsew.w),
				new google.maps.LatLng(nsew.s, nsew.e),
				new google.maps.LatLng(nsew.n, nsew.e),
				new google.maps.LatLng(nsew.n, nsew.w)
			];
		options.path = path;
		google.maps.Polygon.call(I, options);
	}
	
	Polyrect.prototype = new google.maps.Polygon();

})();



(function(){

	if(typeof tbelt ==="undefined") window["tbelt"]={};
	if(typeof tbelt.gmaps === "undefined") tbelt.gmaps={};

	var GOOGLE = google,
		TBELT = tbelt,
		GMAPS = TBELT.gmaps;
		
	var indexOf = function(item, startIndex){
		for(var i=startIndex||0; i<this.length; i++){
			if(this[i]===item) return i;
		}
		return -1;
	};

	var MarkerHandler = GMAPS.MarkerHandler = function(map){
		var I = this;
		I.markers=[];
		I.visibleMarkers=[];
		if(map!=null) I.setMap(map);
	}
	
	MarkerHandler.prototype = {
		markers:null,		
		groups:null,		
		visibleMarkers:null,
		
		_map:null,
		getMap:function(){
			return this._map;
		},
		setMap:function(map){
			var I = this;
			if(map!=I._map){
				//dissasociate existing map before setting the new one
				if(I._map!=null){
					I.mapEventListeners.unbind(I._map);
					I._map.markerHandler=null;
				}
				I._map=map;
				I._map.markerHandler = I;
				I.mapEventListeners.bind(I._map);
				
				//set all of the markers to use this map
				for(var m=0;m<I.markers.length;m++){
					I.markers[m].setMap(I._map);
				}
			}
			I.refresh();
			return I;
		},
		
		add:function(marker, minZoom, maxZoom, doRefresh){
			var I = this;
			if(minZoom==null) minZoom = (marker.minZoom==null) ? -1 : marker.minZoom;
			if(maxZoom==null) maxZoom = (marker.maxZoom==null) ? 99 : marker.maxZoom;
			if(doRefresh==null) doRefresh=false;
			
			var addm=function(m){
				m.minZoom=minZoom;
				m.maxZoom=maxZoom;
				I.markers.push(m);
			}
			if(marker.length){
				for(var m=0; m<marker.length; m++)
					addm(marker[m]);
			}else addm(marker);
			
			if(doRefresh) I.refresh();
			
			return I;
		},
		
		remove:function(marker){
			var I = this,
			idx = -1;
			if(typeof(marker)=="number"){
				idx=marker;
				marker = I.markers[idx];
			}else{
				idx = indexOf.call(I.markers, marker);
			}			
			if(idx>-1 && idx < I.markers.length){
				marker.setMap(null);
				I.markers.splice(idx,1);
			}
			return I;
		},
		
		hideAll:function(){
			for(var m=0; m<this.markers.length;m++){
				this.markers[m].setVisible(false);
			}
		},
		
		
		
		refresh:function(){
			var I = this,
			bounds = I._map.getBounds(),
			zoom = I._map.getZoom();
			I.visibleMarkers=[];
			for(var m=0; m<I.markers.length;m++){
				var marker = I.markers[m],
				isVisible = marker.getVisible();
				//if the marker is not already visible, is in the viewport, and is within the min/max zoom range, show it
				if(bounds.contains(marker.getPosition()) && (zoom >= marker.minZoom && zoom <= marker.maxZoom)){
					if(marker.getMap()==null) marker.setMap(I._map);
					if(!isVisible) marker.setVisible(true);
					I.visibleMarkers.push(marker);
				}else if(isVisible) marker.setVisible(false);
			}
			
			return I;					
		},
		
		getBounds:function(){
			var I = this,
				bounds = new GOOGLE.maps.LatLngBounds();
			for(var m=0; m<I.markers.length;m++){
				var pos = I.markers[m].getPosition();
					bounds.extend(pos);
			}
			return bounds;
		},
		
		getCenter:function(){
			/*var I = this,
				center=null;
				lat=0.00,
				lng=0.00,
				amount = I.markers.length;
			for(var m=0; m<amount;m++){
				var pos = I.markers[m].getPosition();
				lat+=pos.lat();
				lng+=pos.lng();
			}
			center = new GOOGLE.maps.LatLng(lat/amount,lng/amount);*/
			var center = this.getBounds().getCenter();
			return center;
		},
		
		mapEventListeners:{
			bind:function(map){
				for(var eventName in this){
					if(!(/^(_.*)|(bind)|(unbind)$/i).test(eventName)){
						this["__"+eventName] = GOOGLE.maps.event.addListener(map, eventName, this[eventName]);
					}
				}
			},
			unbind:function(map){
				for(var eventName in this){
					if((/^__.*$/i).test(eventName))
						GOOGLE.maps.event.removeListener(this[eventName]);
				}
			},
			
			_idle:null,
			idle:function(){				
				if(this.markerHandler.groups!=null) this.markerHandler.groups.refresh();
				else this.markerHandler.refresh();
			}
			
			
		}
		
	
	};	
	
	

})();





(function(){
	var GOOGLE = google,
		TBELT = tbelt,
		GMAPS = TBELT.gmaps;
			
			
	/*
	Manages a group of MapsEventListeners
	http://code.google.com/apis/maps/documentation/v3/reference.html#MapsEventListener
	*/
	GMAPS.EventsGroup = function(target, eventsObj){
			
			if(target!=null) {
				this.target = target;			
				if(eventsObj!=null){
					this.bindMulti(eventsObj);
				}			
			}
			
		}
		
	GMAPS.EventsGroup.prototype = {
		target:null,
		
		bind:function(evtName, func){
			this[evtName] = google.maps.event.addListener(this.target, evtName, func);
			this["_"+evtName]=func;
			return this[evtName];
		},
		bindMulti:function(evtsObj){
			for(var eventName in evtsObj){
				this.bind(eventName, evtsObj[eventName]);					
			}
		},
		release:function(evtName){
			//console.log(evtName);
			//console.dir(this[evtName]);
			if(this.hasOwnProperty(evtName) && typeof(this["_"+evtName])=="function" && this[evtName].remove){
				google.maps.event.removeListener(this[evtName]);	
				delete this[evtName];
				delete this["_"+evtName];
			}				
		},
		releaseAll:function(){
			for(var eventName in this){
				this.release(eventName);
			}
		}
		
	};


})();


