function Mapper(id, starter){
	this.ST_ZOOM = starter.zoom || 4;
	this.STLT = starter.min_lat || 24;
	this.EDLT = starter.max_lat || 49;
	this.STLO = starter.min_long || -125;
	this.EDLO = starter.max_long || -66;
	
	this.recenter_on_data = true;
	if(starter.useck){
		this.fromCookie();
		this.recenter_on_data = false;
	}
	this.url = starter.url || "/top/mapper/json/";
	this.bubbler_url = starter.bubbler_url || "/top/mapper/bubble/";
	this.markers = {};
	this._did_zoom_bds = {};
	this.getting = false;
	this._moved = false;
	this._zoomed = false;
	this.bubbleWidth = '500px';
	this.fixed_point =  starter.start_point || false;
	this.fixed_icon =  starter.fixed_icon || false;
	this.reg_actions = typeof(starter.reg_actions) == 'undefined'?true:starter.reg_actions;
	this.googlemap = new google_maps(id, {
		map_type: G_NORMAL_MAP,
		start_zoom: this.ST_ZOOM,
		start_point: starter.start_point || new GLatLng((this.EDLT + this.STLT) / 2, (this.EDLO + this.STLO)/2),
		add_start_marker: false,
		large_map: typeof(starter.large_map) == 'undefined'?true:starter.large_map
	});
	
	this.map = this.googlemap.getMap()
	this.map_width = this.map.getSize()[0];
	this.map_height = this.map.getSize()[1];
	this.markermgr = new GMarkerManager(this.map, { borderPadding: 50, maxZoom: 15, trackMarkers: false });
	if(this.reg_actions){
		GEvent.bind(this.map, "zoomend", this,  this.onZoom);
		GEvent.bind(this.map, "moveend", this,  this.onPan);
		GEvent.addListener(this.map, "load", this, this.onPan);
		GEvent.bind(this.map, 'infowindowclose', this, this.onInfoClose)
	}
	this.on_data_change = starter.on_data_change || null;
	this.pre_data_change = starter.pre_data_change || null;
	this.current_request = null;
	this.gc = null;
	this.addFixedMarker(this.fixed_point);
}
Mapper.prototype.fromCookie = function(){
	var ckB = CookieBits.get('ltopmp')
	if(ckB && ckB.split(",").length == 5){
		var xp = ckB.split(",")
		this.STLT = parseFloat(xp[0]) || 24;
		this.EDLT = parseFloat(xp[1]) || 49;
		this.STLO = parseFloat(xp[2]) || -125;
		this.EDLO = parseFloat(xp[3]) || -66;
		this.ST_ZOOM = parseInt(xp[4]) || 4;
		
	}
}
Mapper.prototype.setCookie = function(){
	var Bstr = this.urlBoundString();
	CookieBits.set('ltopmp', Bstr);
}
Mapper.prototype.countMaker = function(ct, mx){
	var ico = G_DEFAULT_ICON;
	if(this.fixed_point && this.fixed_icon && !this.reg_actions){
		var icon = new GIcon();
		icon.image = this.fixed_icon;
		icon.shadow = "/css/map/s.png";
		icon.iconSize = new GSize(30, 30);
		icon.shadowSize = new GSize(30, 30);
		icon.iconAnchor = new GPoint(6, 20);
		icon.infoWindowAnchor = new GPoint(5, 1);
		return icon;
	}
	var icon = new GIcon();
	if(ct == 1){
		icon.image = "/css/map/m1db.png";
		icon.shadow = "/css/map/s.png";
		icon.iconSize = new GSize(20, 34);
		icon.shadowSize = new GSize(37, 34);
		icon.iconAnchor = new GPoint(6, 20);
		
		icon.iconSize = new GSize(15, 26);
		icon.shadowSize = new GSize(26, 26);
		
		icon.iconAnchor = new GPoint(-7, 20);
		icon.infoWindowAnchor = new GPoint(5, 1);
	}else{
		var MX_S = 80;
		var MN_S = 15;
		if(!ct){ return ico;}
		var sz = ct;
		var ico_c = ct;
		if(ico_c >= 99){ ico_c = 99; }
		if(!mx || 1){ 
			if(ct >= 99){ ct = 99; }
			sz = ct;
		}else{
			sz = MX_S * ct / mx ;
		}
		icon.image = "/css/map/bluedot.png";
		/*if(ico_c < 50){
			sz = 45;
			icon.image = "/css/map/bluedot_mm.png";
		}
		if(ico_c < 10){
			sz = MN_S;
			icon.image = "/css/map/bluedot_fm.png";
		}*/
		if(sz < MN_S){ 
			sz = MN_S; 
		}
		if(sz > MX_S){ 
			sz = MX_S; 
		}
		icon.iconSize = new GSize(sz, sz);
		icon.iconAnchor = new GPoint(sz/2, sz/2);
		icon.infoWindowAnchor = new GPoint(sz/2, sz/2);
	}
	/*
	if(ct == 1){
		icon.image = "/css/map/m1db.png";
	}else{
		icon.image = "/css/map/m" + ct + ".png";
	}
	icon.shadow = "/css/map/s.png";
	icon.iconSize = new GSize(20, 34);
	icon.shadowSize = new GSize(37, 34);
	icon.iconAnchor = new GPoint(6, 20);
	*/
	
	return icon;
}
Mapper.prototype.getAddr = function(addr){
	//add our start maker if around
	var obj = this;
	function cb(response){
	  if (!response || response.Status.code != 200) {
		alert("Sorry, we were unable to find that address");
	  } else {
		var pl = response.Placemark;
		place = pl[0];
		var aa = place.AddressDetails.Accuracy;
		point = new GLatLng(place.Point.coordinates[1],
							place.Point.coordinates[0]);
		//aa is in the range of 1 ... 6 (6 = super zoom, 1 = no zoom)
		var z = 10;
		if(aa >= 5){
			z = 12;
		}
		if(aa <= 3){
			z = 8;
		}
		if(aa <= 2){
			z = 3;
		}
		obj.map.setCenter(point, z);
		//obj.onPan();
	  }
	}
	if(addr){
		if(!obj.gc){
			obj.gc = new GClientGeocoder();
		}
		obj.gc.getLocations(addr, cb)
	}
}

Mapper.prototype.addFixedMarker = function(glatlng){
	//add our start maker if around
	if(glatlng){
		var lt = glatlng.lat();
		var lg = glatlng.lng();
		var oo = {'grid_min_lat': lt - 0.001, 'grid_max_lat':lt + 0.001, 'grid_min_long':lg - 0.001, 'grid_max_long':lg + 0.001};
		mk = this.makeMarker(lt,lg, oo)
		this.map.addOverlay(mk)
	}
}
Mapper.prototype.onInfoClose = function(){
	var obj = this;
	function tt(){
		if(obj.cur_center){
			obj.map.panTo(obj.cur_center)
		}
		obj.UserInfoOn = false;
		obj.getting = false;
		obj.showingOverlay = false;
		window.clearTimeout(obj.closeTimer);
	}
	//do a timeout as we can switch from overlay to overlay and we do not wish to clear he thing
	obj.closeTimer = setTimeout(tt, 200)
}

Mapper.prototype.userInnerWindow = function(idx, gpoint){
	function happy(o){
		try{
			if(o.responseText){
				$('map_multiuser_bubble').parentNode.innerHTML = o.responseText.trim();
			}
		}catch(e){}
	}
	function failed(o){}
	var url = this.bubbler_url;
	url += gpoint;
	if(typeof(idx) != 'undefined'){ url += "/?idx=" + parseInt(idx); }
	var callback = { success:happy,failure:failed, timeout: Organizer.timeOutTime }; 
	var request = Organizer.connect.asyncRequest('GET', url, callback, ""); 
}


Mapper.prototype.makeMarker = function(llat, llong, opts){
	opts = opts || {};
	var ico = this.countMaker(opts['count'] || null, opts['max_count'] || null);
	var ct = opts['count'] || null
	opts['icon'] = ico;
	var mrk;
	mrk = new GMarker(new GLatLng(llat, llong), opts);
	mrk.disableDragging()
	mrk.mapper = this;
	var mpr = this;
	GEvent.addListener(mrk, "click", function() {
		mrk.mapper.cancelClose = true;
		mpr.UserInfoOn = true;
		if(mrk.mapper.closeTimer){
			window.clearTimeout(mrk.mapper.closeTimer)
		}
		mrk.mapper.getting = true;
		mrk.mapper.showingOverlay = true;
		mrk.mapper.cur_center = mrk.mapper.map.getCenter()
		function happy(o){
			try{
				if(o.responseText){
					mrk.openInfoWindowHtml(o.responseText.trim());
				}
			}catch(e){}
			if(mpr.currentInfoMarker){
				mpr.currentInfoMarker.remove();
			}
		}
		function failed(o){}
		var idx;
		var url = mpr.bubbler_url;
		if(opts['grid_min_lat']){
			url += opts['grid_min_lat'] + "," + opts['grid_max_lat'] +"," +opts['grid_min_long'] +"," +opts['grid_max_long'] +"," + mrk.mapper.map.getZoom()
			if(opts['user_id']){
				url += "," + opts['user_id'];
			}
		}
		if(typeof(idx) != 'undefined'){ url += "?idx=" + parseInt(idx); }
		var callback = { success:happy,failure:failed, timeout: Organizer.timeOutTime }; 
		var request = Organizer.connect.asyncRequest('GET', url, callback, ""); 
	});
	function infoDiv(latlng, msg){
		if($('info_div_ct')){
			$('info_div_ct').parentNode.removeChild($('info_div_ct'));
		}
		div = document.createElement("div");
		div.id = 'info_div_ct';
		this.orig_p = latlng;
		
		if(latlng){
			this.pos = mpr.map.fromLatLngToDivPixel(this.orig_p);
			div.style.top = this.pos.y + "px";
			div.style.left = this.pos.x + "px";
		}
		div.style.display = 'block';
		if(msg){
			this.msg = msg;
			div.innerHTML = msg;
		}
		this.div = div;
		this.div.style['z-index'] = 99999;
		Organizer.insert(mpr.map.getPane(G_MAP_MAP_PANE), this.div);
	}
	
	infoDiv.prototype = new GOverlay();
	infoDiv.prototype.copy  = function(){
		return new infoDiv(this.orig_p, this.msg)
	}
	infoDiv.prototype.redraw = function(force){}
	infoDiv.prototype.initialize = function(map) {
		this.map = map;
	}
	infoDiv.prototype.remove = function() {
		 if(this.div && this.div.parentNode){
			this.div.parentNode.removeChild(this.div);
		}
	}
	
	if(ct && ct > 1){
		GEvent.addListener(mrk, "mouseover", function() {
			try{
			if(mpr.UserInfoOn){ return ; }
			mpr.currentInfoMarker = new infoDiv(mrk.getLatLng(), ct + " People")
			mpr.map.addOverlay(mrk.currentInfoMarker);
			}catch(e){}
		});
		GEvent.addListener(mrk, "mouseout", function() {
			try{
			mpr.currentInfoMarker.remove();
			}catch(e){}
		});
	}
	return mrk;
}
Mapper.prototype.boundsStr = function(){
	var bds = this.map.getBounds();
	//round to the nearest .00
	var str_bds = [Math.ceil(100 * bds.getNorthEast().lat())/100, Math.ceil(100 * bds.getSouthWest().lat())/100,  Math.ceil(100 * bds.getNorthEast().lng())/100, Math.ceil(100 * bds.getSouthWest().lng())/100];
	return str_bds.join(",")
}
Mapper.prototype.addPoints= function(o){
	if(!o.responseText) return false;
	try{
		eval ( "var info = " + o.responseText);
	}catch(e){
		return;
	}
	var obj = o.argument[0];
	var oldz = o.argument[1];
	var newz = o.argument[2];
	
	
	if(!info.locations){
		obj.addFixedMarker(obj.fixed_point);
		return false;
	}

	var makrs = [];
	var loc = info.locations;
	var len = loc.length;
	loc.sort(function(a,b){ return b['count'] < a['count']?1:-1});
	obj.map.clearOverlays();
	var max_ct = 0;
	if(len){
		max_ct = loc[len - 1]['count'];
	}
	for(var i =0;i<len;i++){
		//if the fixed point is one of these, ignore it
		if(loc[i].ave_lat){
			loc[i]['max_count'] = max_ct;
			var k = loc[i].ave_lat +"|"+ loc[i].ave_long + "|" + oldz + "|" + newz;
			var mk = obj.makeMarker(parseFloat(loc[i].ave_lat), parseFloat(loc[i].ave_long), loc[i]);
			obj.map.addOverlay(mk);
			obj.markers[k] = mk;
		}else if(loc[i].geo_lat){
			var k = loc[i].geo_lat +"|"+ loc[i].geo_long + "|" + oldz + "|" + newz;
			makrs.push(obj.makeMarker(parseFloat(loc[i].geo_lat), parseFloat(loc[i].geo_long)));
			obj.markers[k] = 1;
		}
	}
	obj.addFixedMarker(obj.fixed_point);
	//obj.markermgr.addMarkers(makrs, newz, newz);
	//obj.markermgr.refresh();
	obj.getting = false
	if(obj.on_data_change){
		var z = obj.map.getZoom();
		var bnds = obj.boundsStr()
		obj.on_data_change(obj, bnds, z);
	}
};

Mapper.prototype.onZoom = function(oldz, newz){
	this._zoomed = true;
	//if(this.getting) return
	this.getting  = true;
	var ob = this;
	var bnds = this.boundsStr() + "," + newz
	var c_key = oldz +"|"+newz+"|"+bnds
	this.setCookie();
	if(this.pre_data_change){
		this.pre_data_change(bnds, newz, oldz);
	}
	if(this._did_zoom_bds[c_key])
		return;
	var callback = { success:this.addPoints,failure:void(0), timeout: Organizer.timeOutTime, argument:[ob, oldz, newz, bnds] }; 
	var sUrl = this.url + bnds;
	if(this.current_request){
		Organizer.connect.abort(this.current_request);
		this.current_request = null;
	}
	this.current_request = Organizer.connect.asyncRequest('GET', sUrl, callback, ""); 
	this._did_zoom_bds[c_key] = 1;
	this._zoomed = false;
	
};

Mapper.prototype.urlBoundString = function(){
	var b = this.boundsStr();
	var z = this.map.getZoom();
	return b + "," + z;
}

Mapper.prototype.onPan = function(){
	this._moved = true;
	if(this.getting){ return; }
	this.getting  = true;
	var ob = this;
	var z = this.map.getZoom();
	var bnds = this.boundsStr() + "," + z
	var c_key = z +"|"+z+"|"+bnds
	this.setCookie();
	if(this._did_zoom_bds[c_key])
		return;
	if(this.pre_data_change){
		this.pre_data_change(bnds, newz, oldz);
	}
	var callback = { success:this.addPoints,failure:void(0), timeout: Organizer.timeOutTime, argument:[ob, z, z, bnds] }; 
	var sUrl = this.url + bnds;
	if(this.current_request){
		Organizer.connect.abort(this.current_request);
		this.current_request = null;
	}
	this.current_request = Organizer.connect.asyncRequest('GET', sUrl, callback, ""); 
	this._did_zoom_bds[c_key] = 1;
	this._moved = false;
};
