// JavaScript Document
// Requires Prototype.js

if(!maps)
	var maps = new Object();

if(window['placemarks'] == 'undefined')
	var placemarks = new Object();

//Object Representing a Simpleview Map
function Map(name,divname,defaultLat,defaultLng,defaultZoom){
	this.defaultSettings = {
		typeControl: new GMapTypeControl(),
		control: new GSmallMapControl(),
		defaultLat: defaultLat,
		defaultLng: defaultLng,
		defaultZoom: defaultZoom,
		defaultPoint: new GLatLng(defaultLat,defaultLng)
	}

	this.settings = {
		typeControl: new GMapTypeControl(),
		control: new GSmallMapControl(),
		defaultLat: defaultLat,
		defaultLng: defaultLng,
		defaultZoom: defaultZoom,
		defaultPoint: new GLatLng(defaultLat,defaultLng)
	}
	
	this.bounds = new GLatLngBounds();
	this.name = name;
	this.gmap = null;

	this.collections = new Object();
	this.defaultCollection = null;

	this.allFilters = new Object();
	this.cancelCentering = false;


	//Create An Alias
	var map = this;

	this.visibleTips = new Array();
	
	this.sortCollection = function(collection,sortBy){
	this.collections[collection].sortBy(sortBy);
	}

	this.initCustomIcons = function(){
		
		//var imgroot = "../..";
		
		this.iconStyles = {
			'default':{
				image: imgroot + '/includes/images/shell/Google/defaultgreen_icon:label.png',
				shadow: imgroot + '/includes/images/shell/Google/default_shadow.png',
				smimage: imgroot + '/includes/images/shell/google/default_sm_icon:label.png',
				iconSize: new GSize(29.0, 34.0),
				shadowSize: new GSize(47.0, 34.0),
				iconAnchor: new GPoint(15.0, 34.0),
				infoWindowAnchor: new GPoint(14.0, 17.0)
			},
			
			'defaultblack':{
				image: imgroot + '/includes/images/shell/google/default_icon:label.png',
				shadow: imgroot + '/includes/images/shell/google/default_shadow.png',
				iconSize: new GSize(29.0, 34.0),
				shadowSize: new GSize(47.0, 34.0),
				iconAnchor: new GPoint(15.0, 34.0),
				infoWindowAnchor: new GPoint(14.0, 17.0)
			},
			
			'default_purple':{
				image: imgroot + '/includes/images/google/default_purple/default_purple_icon:label.png',
				shadow: imgroot + '/includes/images/google/default_shadow.png',
				iconSize: new GSize(29.0, 34.0),
				shadowSize: new GSize(47.0, 34.0),
				iconAnchor: new GPoint(15.0, 34.0),
				infoWindowAnchor: new GPoint(14.0, 17.0)
			},
			
			'defaultblackfolder':{
				image: imgroot + '/includes/images/google/defaultblack/defaultblackfolder.png',
				shadow: imgroot + '/includes/images/google/defaultblack/defaultblack_shadow.png',
				iconSize: new GSize(29.0, 34.0),
				shadowSize: new GSize(47.0, 34.0),
				iconAnchor: new GPoint(15.0, 34.0),
				infoWindowAnchor: new GPoint(14.0, 17.0)
			},
			
			'redpushpin':{
				image:imgroot + '/includes/images/shell/Google/red-pushpin.png',
				shadow:imgroot + '/includes/images/shell/Google/pushpin_shadow.png',
				iconSize: new GSize(32.0, 32.0),
				shadowSize: new GSize(49.0, 32.0),
				iconAnchor: new GPoint(6, 32.0),
				infoWindowAnchor: new GPoint(16.0, 16.0)
			},
			
			'pushpin':{
				image:imgroot + '/includes/images/shell/google/pushpin_A.png',
				shadow:imgroot + '/includes/images/shell/google/default_shadow.png',
				iconSize: new GSize(32.0, 32.0),
				shadowSize: new GSize(49.0, 32.0),
				iconAnchor: new GPoint(6, 32.0),
				infoWindowAnchor: new GPoint(16.0, 16.0)
			}	
		};
	}

	//Initialize Map
	this.create = function(useClustering){
		logit('Called: create');
		this.initCustomIcons();
		map.div = $(divname);
		map.gmap = new GMap2(map.div);

		if(!this.cancelCentering){
			map.gmap.setCenter(map.settings.defaultPoint);
			map.gmap.setZoom(map.settings.defaultZoom);
		}

		if(map.settings.control != null){
			map.control = map.settings.control;
			map.gmap.addControl(map.control);
		}
		if(map.settings.typeControl != null){
			map.typeControl  = map.settings.typeControl;
			map.gmap.addControl(map.typeControl);
			map.gmap.addMapType(G_PHYSICAL_MAP);
		}
		if(useClustering){
			this.useClustering = true;
			this.zoomGroups = new Array();
			logit('Initializing zoomgroups');
			this.grouped = new OverlayCollection('grouped',1000);
			this.grouped.useLabels = false;
			this.grouped.labelType = 'Numeric';
			this.grouped.getIconStyle = function(placemark){return 'default';};
			this.grouped.clickFunction = function(marker){return true;}
			this.grouped.getInfoHTML = function(placemark){return placemark.numPins + ' properties were found at this location.  Click <a href="javascript:zoomToGroup(\'' + map.name + '\',' + placemark.groupIndex + ')">here</a> to zoom in on this group';};
			
			this.grouped.getToolTip = function(placemark){
				return 'Click to Zoom In (' + placemark.numPins + ')';
			}
			
			this.addCollection(this.grouped);
		}
		else
			this.useClustering = false;

		//Register Map with main array
		maps[map.name] = map;
		GEvent.addListener(this.gmap, "zoomend", function() { 
			if(this.allFilters.zoomFilter && !this.allFilters.zoomFilter.cancelled){
				this.allFilters.zoomFilter.run();
			}
		}.bind(this));
		logit('End: create');
	}
	
	this.removeOverlay = function(overlay){
		if(overlay.closeInfoWindow)
			overlay.closeInfoWindow();
		this.gmap.removeOverlay(overlay);
		if(overlay.tooltip){
			this.gmap.removeOverlay(overlay.tooltip);
		}
	}
	
	this.addCollection = function(oc,isDefault){
		logit('Called: addCollection: ' + oc.name);
		this.collections[oc.name] = oc;
		if(isDefault || this.defaultCollection == null)
			this.defaultCollection = oc;
		oc.map = this;
		logit('End: addCollection: ' + oc.name);		
		return oc;
	}

	//Set Current Maps Control
	this.setControl = function(control){
		if(map.settings.control)
			map.gmap.removeControl(map.settings.control);

		if(control)
			map.gmap.addControl(control);
		this.settings.control = control;
	}

	this.setCenter = function(){
		logit('Called: setCenter');
		map.bounds = new GLatLngBounds();
		var p;		
		for (var i in this.collections){
			var c = this.collections[i];
			map.bounds = addBounds(map.bounds,c.calcBounds());
		}
		if(map.bounds.isEmpty()){
			p = map.settings.defaultPoint;
		}
		else{
			p = map.bounds.getCenter();
		}		
		map.gmap.setCenter(p);
		logit('End: setCenter');		
	}
	
	this.getBounds = function(){
		var bounds = new GLatLngBounds();
		for (var i in this.collections){
			var c = this.collections[i];
			var cbounds = c.calcBounds();
			bounds = addBounds(bounds,cbounds);
		}
		return bounds;
	}
	
	this.setBoundsCenterAndZoom = function(){
		logit('Called: setBoundsCenterAndZoom');
		map.bounds = this.getBounds();
		if(!map.bounds.isEmpty()){
			var p = map.bounds.getCenter();
			var z = map.gmap.getBoundsZoomLevel(map.bounds);
		}
		else{
			var p = map.settings.defaultPoint;
			var z = map.settings.defaultZoom;
		}
		
		
		
		
		map.gmap.setCenter(p);
		if(this.allFilters.zoomFilter)
			this.allFilters.zoomFilter.cancelled = true;
		
		if (z > 14) {
			z = 14;
		}
		
		map.gmap.setZoom(z);
		if(this.allFilters.zoomFilter)		
			this.allFilters.zoomFilter.cancelled = false;
		logit('End: setBoundsCenterAndZoom');
	}

	this.removeOverlays = function(collection){
		logit('Called: removeoverlays - ' + collection.name);
		if(collection.name == 'grouped')
			logit('removing overlays for: ' + collection.name);
		for(var i in collection.data){
			var p = collection.data[i];
			if(p.marker){
				p.marker.closeInfoWindow();
				//GEvent.removeListeners();
				this.removeOverlay(p.marker);
				//delete p.marker;
				logit('Removing Marker for: ' + p.name);
			}
		}
		logit('End: removeoverlays - ' + collection.name);		
	}


	//Remove all placemarks from the map
	this.removePlacemarks = function(collection,cancelHandlers){
		logit('Called: removeplacemarks - ' + collection.name);
		if(this.useClustering)
			this.ungroupMarkers();
		this.removeOverlays(collection);
		collection.clearAll();
		if(!cancelHandlers)
			collection.execHandler('addremove','removeplacemarks');
		logit('End: removeplacemarks - ' + collection.name);
	}

	this.removePlacemark = function(placemark,collection,cancelHandlers){
//		logit('Called: removePlacemark - ' + placemark.name + ' - ' + collection.name);
		if(!collection)
			collection = this.defaultCollection;

		if(placemark.marker){
			this.removeOverlay(placemark.marker);
		}
		collection.clear(placemark);
		if(!cancelHandlers){
			collection.execHandler('addremove','removeplacemark');
		}
//		logit('End: removePlacemark - ' + placemark.name + ' - ' + collection.name);		
	}

	//Add a Simple Marker
	this.addPlacemark = function(placemark,collection,cancelHandlers){
		if(!collection)
			collection = map.defaultCollection;

		collection.set(placemark);

		if(!cancelHandlers){
			collection.execHandler('addremove','addplacemark');
		}
	}

	this.showPlacemark = function(placemark,cancelHandlers){
		var marker = placemark.marker;
		var collection = placemark.collection;
		map.bounds.extend(marker.getPoint());
		map.gmap.addOverlay(marker);

		GEvent.addListener(marker,"mouseover",function(){
				var imgsrc = this.getIcon().image;
				var newsrc = imgsrc.replace('_hover','');
				var newsrc = newsrc.replace('.png','');
				var components = newsrc.split('_');
				if(components.length > 1){
					label = components[components.length-1];
					components = components.splice(components,components.length-1,1);
					var str = components.join('_');
					newsrc = str + '_hover' + '_' + label + '.png';
				}
				else
					newsrc = newsrc + '_hover.png';
				this.setImage(newsrc);	
				//GLog.write(newsrc);
			});

		GEvent.addListener(marker,"mouseout",function(){
				var imgsrc = this.getIcon().image;
				var newsrc = imgsrc.replace('_hover','');
				this.setImage(newsrc);	
				//GLog.write(newsrc);
			});

		if(marker.tooltip){
			logit('adding tooltip - ' + marker.title);
			map.gmap.addOverlay(marker.tooltip);
			
			GEvent.addListener(marker, "mouseover", 
				function() {
					this.tooltip.show();
					//map.visibleTips.push(this.tooltip);
					return false;
				}
			)
			
			GEvent.addListener(marker, "mouseout",
				function() {
					this.tooltip.hide();
					//$P(map.visibleTips).map(function(itm){itm.hide()});
					//map.visibleTips = new Array();
					return false;
				}
			)
		}
		if(placemark.name.indexOf('group_') >= 0){
			GEvent.addListener(marker, "click", function() {
				map.zoomToGroup(marker.placemark.groupIndex);
			});
			
		}
		else if(marker.myInfoHTML){
			GEvent.addListener(marker, "click", function() {
				map.gmap.setCenter(marker.getPoint());
				marker.openInfoWindowHtml(marker.myInfoHTML);
				collection.execHandler('openinfobubble',placemark);
			});
		}

		if(!cancelHandlers)
			collection.execHandler('showhide','showplacemark');
//		logit('End: showplacemark - ' + placemark.name);			
	}
	this.sendBack = function(){
		if(!this.zindex)
			this.zindex = 100;
		this.zindex++;
		return this.zindex;
	}

	//Ensures all placemarks in this array have a marker created
	this.preparePlacemarks = function(placemarks,oc){
		for (var i = 0; i < placemarks.length; i++){
			var placemark = placemarks[i];

			if(oc.useLabels){
				placemark.label = (oc.labelType == 'Alpha' ? chr(i + asc('A')) :  (placemark.type && placemark.type == 'group') ? placemark.numPins : (i + 1));
			}
			else{
				placemark.label = '';
			}
			
			//Just trying to be careful with this one
			if(placemark.marker){
				logit('found marker for: ' + placemark.name + ' - removing');
			}
			else{
				//Assign icon
				placemark.icon = this.createCustomIcon(oc.getIconStyle(placemark),placemark.label);
				
				//Get Text for ToolTip
				placemark.tooltip = oc.getToolTip(placemark);
				placemark.marker = new GMarker(new GLatLng(placemark.latitude,placemark.longitude),{icon:placemark.icon,zIndexProcess:this.sendBack.bind()});
				placemark.marker.title = placemark.tooltip;
				placemark.marker.tooltip = new Tooltip(placemark.marker,placemark.tooltip,4);
	
				//Add Reverse Lookup - Memory leak Issue?
				placemark.marker.placemark = placemark;
				placemark.marker.myInfoHTML = oc.getInfoHTML(placemark);
			}
			if(oc.name != 'grouped' && !placemark.isClustered){
				placemark.isClustered = false;
				placemark.cluster = null;
			}
			logit('setting marker for: ' + placemark.name);
		}
		return placemarks;
	}

	this.showPlacemarks = function(oc,page,cancelHandlers){
		logit('Called: Showplacemarks - ' + oc.name);
		this.removeOverlays(oc);
		if(!page)
			page = 1;

		oc.currPage = page;

		var placemarks = oc.getPage(page);

		//Prepare placemarks
		this.preparePlacemarks(placemarks,oc);

		//Set the center and zoom of map
		if(!this.cancelCentering){
			map.setBoundsCenterAndZoom();
		}

		//Convert this page to groups where necessary
		if(this.useClustering && oc != this.grouped){
			this.groupPlacemarks(placemarks);
			this.showGrouped();			
		}

		//Show them all
		for (var i = 0; i < placemarks.length; i++){
			var placemark = placemarks[i];
			if(!placemark.isClustered){
				var p = new GLatLng(placemark.latitude,placemark.longitude);
				placemark.icon = this.createCustomIcon(oc.getIconStyle(placemark),placemark.label);
				var m = new GMarker(new GLatLng(placemark.latitude,placemark.longitude),{icon:placemark.icon,zIndexProcess:this.sendBack.bind()});
				m.title = placemark.tooltip;
				m.tooltip = new Tooltip(m,placemark.tooltip,4);
				m.myInfoHTML = oc.getInfoHTML(placemark);
				//Add Reverse Lookups
				m.placemark = placemark;
				placemark.marker = m;
				this.showPlacemark(placemark,true);
			}
		}

		if(!cancelHandlers)
			oc.execHandler('showhide','showplacemarks');
		logit('End: Showplacemarks - ' + oc.name);
	}

	this.drawGrid = function(){
		var cols = 13;
		var rows = 7;
		var groupThreshold = 3;
		var width = this.gmap.getSize().width;
		var height = this.gmap.getSize().height;
		var stepX = width / cols;
		var stepY = height / rows;
		var markerarray = new Array();
		var overloaded = new Array();


		if(this.gridoverlays){
			$P(this.gridoverlays).map(
				function(itm){
					this.removeOverlay(itm);
				}.bind(this)
			);
			this.gridoverlays = new Array();
		}
		else
			this.gridoverlays = new Array();

		for (var i = 0; i < cols; i++){
			var xlow = i * stepX;
			var xhigh = (i+1) * stepX;
			for (var j =0; j < rows; j++){
				var ylow = j * stepY;
				var yhigh = (j+1) * stepY;
				var points = new Array();
				points.push(this.gmap.fromContainerPixelToLatLng(new GPoint(xlow,ylow)));
				points.push(this.gmap.fromContainerPixelToLatLng(new GPoint(xhigh,ylow)));
				points.push(this.gmap.fromContainerPixelToLatLng(new GPoint(xhigh,yhigh)));
				points.push(this.gmap.fromContainerPixelToLatLng(new GPoint(xlow,yhigh)));
				points.push(this.gmap.fromContainerPixelToLatLng(new GPoint(xlow,ylow)));
				var box = new GPolygon(points,'#0000ff', 1, 1, '#0000ff', 0.2);
				this.gmap.addOverlay(box);
				this.gridoverlays.push(box);
			}
		}
	}

	this.groupPlacemarks = function(placemarks){
		logit('Called: groupPlacemarks');
		var cols = 13;
		var rows = 7;
		var groupThreshold = 3;
		var width = this.gmap.getSize().width;
		var height = this.gmap.getSize().height;
		var stepX = width / cols;
		var stepY = height / rows;
		var markerarray = new Array();
		var overloaded = new Array();
//		this.drawGrid();

		for(var i = 0; i < cols; i++){
			for (var j = 0; j < rows; j++){
				var idx = j * cols + i;
					markerarray[idx] = {
					markers:new Array(),
					cnt:0,
					totalX:0,
					totalY:0,
					centerX: i * stepX + (stepX / 2.0),
					centerY: j * stepY + (stepY / 2.0)
				}
			}
		}

		//Get the current page and drop all placemarks into their grid blocks, update summary stats
		for (var i = 0; i < placemarks.length; i++){
			var p = placemarks[i];
			var loc = this.gmap.fromLatLngToContainerPixel(p.marker.getPoint());
			var posx = Math.floor(loc.x / stepX);
			var posy = Math.floor(loc.y / stepY);
			var idx = posy * cols + posx;
			if(idx >= 0 && idx < markerarray.length){
				markerarray[idx].cnt++;
				markerarray[idx].markers.push(p);
				markerarray[idx].totalX += loc.x;
				markerarray[idx].totalY += loc.y;
			}
		}
		
		//Find the overloaded grid blocks
		for (var i = 0; i < (rows * cols); i++){
			if(markerarray[i].cnt > groupThreshold){
				markerarray[i].avgX = markerarray[i].totalX / markerarray[i].cnt;
				markerarray[i].avgY = markerarray[i].totalY / markerarray[i].cnt;
				markerarray[i].position = this.gmap.fromContainerPixelToLatLng(new GPoint(markerarray[i].avgX,markerarray[i].avgY));
				overloaded.push(markerarray[i]);
			}
		}

		//Mark these placemarks as grouped
		for(var i = 0; i < overloaded.length; i++){
			var cluster = overloaded[i];
			for (var j = 0; j < cluster.markers.length; j++){
				cluster.markers[j].isClustered = true;
				cluster.markers[j].cluster = cluster;
				cluster.groupIndex = i;
			}
		}

		this.zoomGroups = overloaded;

		logit('End: groupPlacemarks');		
		
	}

	this.ungroupMarkers = function(){
		logit('Called: ungroupmarkers');
		logit('this.grouped.length is: ' + this.grouped.length + ' before ungroup markers');		
		this.removeOverlays(this.grouped);
		for(var i = 0; i < this.zoomGroups.length; i++){
			var cluster = this.zoomGroups[i];
			for (var j = 0; j < cluster.markers.length; j++){
				var p = cluster.markers[j];
				p.isClustered = false;
				p.cluster = null;
			}
		}
		this.zoomGroups = new Array();
		this.grouped.clearAll();
		logit('this.grouped.length is: ' + this.grouped.length + ' after ungroup markers');
		logit('End: ungroupmarkers');			
	}
	
	this.showGrouped = function(){
		logit('Called: showgrouped - ' + this.zoomGroups.length);
		var groups = this.zoomGroups;
		var backup = this.cancelCentering;
		this.cancelCentering = true;		
		for (var i = 0; i < groups.length; i++){
			var pnt = new GPoint(groups[i].centerX,groups[i].centerY);
			var translated = this.gmap.fromContainerPixelToLatLng(pnt);
			var p = {
				prikey:'group_' + i,
				name:'group_' + i + ' - ' + groups[i].cnt,
				iconStyle: 'default',
				type: 'group',
				numPins: groups[i].cnt,
				latitude: translated.lat(),
				longitude: translated.lng(),
				groupIndex: i,
				point: translated,
				zoomGroup: groups[i]
			};
			groups[i].placemark = p;
			this.addPlacemark(p,this.grouped,true);
		}

		this.showPlacemarks(this.grouped,1,true);

		this.cancelCentering = backup;
		logit('End: showgrouped - ' + groups.length);		
	}

	this.zoomToGroup = function(groupIndex){
		logit('Called: zoomtogroup - ' + groupIndex);
		var group = this.zoomGroups[groupIndex];
		group.placemark.marker.closeInfoWindow();
		var bounds = new GLatLngBounds();
		logit('zooming to group based on ' + group.markers.length + ' markers');
		var minlat = 1000;
		var maxlat = -1000;
		for (var i = 0; i < group.markers.length; i++){
			var p = group.markers[i];
			var pnt = new GLatLng(p.latitude,p.longitude);
			bounds.extend(pnt);
		}
		var p = bounds.getCenter();
		var z = map.gmap.getBoundsZoomLevel(bounds);
		logit('setting zoom to: ' + z + ' from ' + map.gmap.getZoom());
		this.gmap.setCenter(p);
		this.gmap.setZoom(z);
//		if(this.allFilters.zoomFilter)
//			this.allFilters.zoomFilter.run();
		logit('End: zoomtogroup - ' + groupIndex);			
	}
	
	this.zoomAndGoTo = function(placemark){
		var cluster = placemark.cluster;
		this.zoomToGroup(cluster.groupIndex);
		goToPlacemark(placemark.prikey);
	}

	this.addPlacemarks = function(placemarks,collection,cancelHandlers){
		logit('Called: addPlacemarks - ' + collection.name);
		for (var i = 0; i < placemarks.length; i++){
			var g = placemarks[i];
			//Add Reverse Lookups
			map.addPlacemark(g,collection,true);
		}
		if(!cancelHandlers)
			collection.execHandler('addremove','addplacemarks');
		logit('End: addPlacemarks - ' + collection.name);			
	}

	this.createCustomIcon = function (style,label){
		var iconStyle = clone(this.iconStyles[style]);
		iconStyle.image = iconStyle.image.replace('icon:label',label);
		if (iconStyle.smimage) iconStyle.smimage = iconStyle.smimage.replace('icon:label',label);
		var icon = new GIcon(iconStyle);
		return icon;
	}
	
	this.registerFilter = function(name,filter){
		this.allFilters[name] = filter;
	}
	
	this.initFilters = function(){
		for(var i in this.allFilters){
			this.allFilters[i].run();
		}
	}
	
	this.getVisiblePlacemarks = function(){
		var arr = new Array();
		for (var i in this.collections){
			var p = this.collections[i].getPage(this.collections[i].currPage);
			for(var j = 0; j < p.length; j++){
				arr.push({name:p[j].name,collection:this.collections[i].name,prikey:p[j].prikey});
			}
		}
		return arr;
	}
	//Finds the edges of the icons and sets an adjust zoom level flag if the icon will get cut off
	this.adjustForIcons = function(marker){
		var container = this.gmap.getContainer();
		var containerY = container.scrollHeight;
		var containerX = container.scrollWidth;

		//LatLng of Marker
		var latlng = marker.getLatLng();

		//Convert to Pixel Object
		var divpixels = this.gmap.fromLatLngToContainerPixel(latlng);

		//Placement of icon (in pixels)
		var y = divpixels.y;
		var x = divpixels.x;

		//Width and Height of icon
		var iconWidth = marker.getIcon().iconSize.width;
		var iconHeight = marker.getIcon().iconSize.height;

		//Offset of icon from plotted point
		var anchorWidth = marker.getIcon().iconAnchor.x;
		var anchorHeight = marker.getIcon().iconAnchor.y;

		//Location of top of image (in pixels)
		var iconTop = y - anchorHeight;	
		//Location of bottom of image (in pixels)
		var iconBottom = y + iconHeight - anchorHeight;

		//Location of left edge of icon
		var iconLeft = x - anchorWidth;
		//Location of right edge of icon		
		var iconRight = x + iconWidth + anchorWidth;
		var zoomOut = (iconTop < 0 || iconBottom > containerY || iconLeft < 0 || iconRight > containerX);
		if(zoomOut)
			this.zoomAdjust = -1;
		return zoomOut;
	}
}

/*************************************************
	General Utility Functions
**************************************************/

function addBounds(b1,b2){
	if (b1.isEmpty())
		return b2;
		
	if(b2.isEmpty())
		return b1;

	var southWest = b1.getSouthWest();  
	var northEast = b1.getNorthEast();
	var b3 = new GLatLngBounds();
	b3.extend(southWest);
	b3.extend(northEast);
	var southWest = b2.getSouthWest();  
	var northEast = b2.getNorthEast();
	b3.extend(southWest);
	b3.extend(northEast);
	return b3;
}

function placemarkDistance(p1,p2){
	return distance(parseFloat(p1.latitude),parseFloat(p1.longitude),parseFloat(p2.latitude),parseFloat(p2.longitude));
}

function distance(lat1,lon1,lat2,lon2){
	var R = 6371; // km
	var kpm = 1.609344;  //km per mile
	var dLat = (lat2-lat1).toRad();
	var dLon = (lon2-lon1).toRad(); 
	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
			Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * 
			Math.sin(dLon/2) * Math.sin(dLon/2); 
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
	var d = (R * c) / kpm;
	return d;
}

function parsePlacemarkData(str,p){
	var imgroot = "..";
	
	var fieldlist = ['name','prikey','weburl','addr1','addr2','city','state','zip', 'imagefile','logofile'];
	for (var i = 0; i < fieldlist.length; i++){
		var searchstr = 'placemark:' + fieldlist[i];
		var replacewith = String(p[fieldlist[i]]).substr(0,300);
		while (str.indexOf(searchstr) > 0 && searchstr != replacewith){
			str = str.replace(searchstr,replacewith);
		}
	}
	

	var searchstr = 'placemark:description';
	if(p.description.length > 0){
		var replacewith = p.description;
	}
	else
		var replacewith = '*No Description Available*';

		while (str.indexOf(searchstr) > 0 && searchstr != replacewith){
			str = str.replace(searchstr,replacewith);
		}
	
	if(p.imagefile){
		str = str.replace('placemark:imagefile', p.imagefile.length > 0 ? p.imagefile : 'notavailable.gif');
	}
	else
	str = str.replace('<img class="list-image" src="../includes/images/listings/" />','<img class="list-image" src="../includes/images/listings/notavailable.gif" />');
	str = str.replace('<img class="info-image" src="../includes/images/listings/" />','<img class="info-image" src="../includes/images/listings/notavailable.gif" />');
	
	
	if(p.logofile){
		str = str.replace('placemark:logofile', p.logofile.length > 0 ? p.logofile : 'notavailable.gif');
	}
	else
		str = str.replace('placemark:logofile', 'notavailable.gif');
	
	//Only available when the marker is visible on the map
	if(p.icon){
		str = str.replace('placemark:iconimage',p.icon.image);
		str = str.replace('placemark:sm_iconimage',p.icon.smimage);
	}
	if(p.distance){
		str = str.replace('placemark:distance',p.distance.toFixed(2));
	}
	
	if(p.itinerary == true){
		str = str.replace('Add to Itinerary','<a href="'+ imgroot +'/itinerary/index.cfm">Added to Itinerary</a>');
	}
	else
		str = str.replace('placemark:itinerary','<a href=\"javascript:void(itin_add(' + p.prikey + ', \'cbListing\',\'1\'));\">Add to Itinerary</a>');
		str = str.replace('placemark:itinerary','<span id="itin_' + p.prikey + '"><a href=\"javascript:void(ajaxAddItin(\'..\', ' + p.prikey + ', 1));\">Add to Itinerary</a></span>');
	
	
	
	if (p.weburl.length > 0)
		str = str.replace('placemark:website_redirect', '<a href=\"' + imgroot + '/includes/redirects/webcount.cfm?listingID=' + String(p.prikey) + '&webURL=' + String(p.weburl) + '\" target=\"_blank\">Visit Website</a><br>');
	else
		str = str.replace('placemark:website_redirect', '');
		
	var addr = new Address();
	try{
		addr.loadFromObject(p);
		str = str.replace('placemark:address',addr.toHTMLString());
	}
	catch(ex){
		alert(ex);
	}


	return str;
}

function chr(num){
	return String.fromCharCode(num);
}

function asc(str){
	return str.charCodeAt(0);
}

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); // changed (twice)
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
}

//Itinerary Add Function
function itin_add(listingid)
{
	var imgroot = "..";
	var thisUrl  =  imgroot+'/itinerary/index.cfm';
	var thisData = 'action=ajax_addItin&listingid='+listingid;
	
	
	new Ajax.Request(thisUrl,
		{
			method: 'get',
			parameters: thisData,
			onSuccess: function (response){
				updatePlacemarkItinerary(listingid);
					return true;
			},
		   onFailure: function(response){
			   
			   alert('Could not add to itinerary');
		   }
		}
	);
}

//Itinerary Function
function updatePlacemarkItinerary(prikey){
	for (var i in placemarks){
		var p = findPlacemark(i,prikey);
		if(p != null){
			var tmp2 = $P('itin_' + prikey);
			if(tmp2){
				tmp2.innerHTML = '<span style="color: white">Added to Itinerary</span>';
			}
			p.itinerary = true;
			if(p.marker && p.collection){
				p.marker.myInfoHTML = p.collection.getInfoHTML(p);
			}
		}
	}
	transferFromOverlays(prikey,itineraryOverlays);
	goToPlacemark(prikey);
}

function zoomToGroup(mapname,groupIndex){
	maps[mapname].zoomToGroup(groupIndex);
}

var tabdepth = 0;

function logit(str){
	return;
	var outstr = str;
	if (str.indexOf('End') == 0){
		tabdepth -= 1;
	}
	for (var i = 0; i < tabdepth; i++){
		outstr = '--' + outstr;
	}
	//GLog.write(outstr);
	if(str.indexOf('Called') == 0){
		tabdepth += 1;
	}
}

//Cleanup to mitigate memory leaks resulting from cicular dom references and use of closures
function myUnload(){
	GUnload();
}
