function MapShow () {
	var GMapField = null;
	var GStreetField = null;
	var Title = null;
	var Address = null;
	var GLat = null;
	var GLon = null;
	var GLatField = null;
	var GLonField = null;
	var Zoom = 12;
	var MapStyle = G_NORMAL_MAP;
	var Visible = false;
	var GeocodeStarted = false;
	var GMapObject = null;
	var GStreetObject = null;
	var AddressMarker = null;
	var Bearing = null;
	var PLat = null;
	var PLon = null;

	
	this.Init = function(title, address, gMapID) {
		GMapField = document.getElementById(gMapID);
		Title = title;
		Address = address;
	}
	
	this.SetLatLon = function(gLat, gLon) {
		GLat = gLat;
		GLon = gLon;
	}
	
	this.SetStreetID = function(gStreetID, gStreetButtonID) {
		GStreetField = document.getElementById(gStreetID);
		var GStreetButtonField = document.getElementById(gStreetButtonID);
		if (GLat && GLon) {
			var GStreetClientObject = new GStreetviewClient;
			GStreetClientObject.getNearestPanoramaLatLng(new GLatLng(GLat, GLon),
				function(point) {
					if (point) {
						var NewPoint = new GLatLng(GLat, GLon);
						Bearing = CalcBearing(point.lat(), point.lng(), NewPoint.lat(), NewPoint.lng());
						PLat = point.lat();
						PLon = point.lng();
						GStreetButtonField.style.display = "inline";
					}
				})
		}
	}
	
	this.SetLatLonFields = function(gLatID, gLonID) {
		GLatField = document.getElementById(gLatID);
		GLonField = document.getElementById(gLonID);
	}

	this.DisplayMap = function() {
		Zoom = 12;
		MapStyle = G_NORMAL_MAP;
		ShowMap();
	}

	this.DisplayAerial = function() {
		Zoom = 18;
		MapStyle = G_HYBRID_MAP;
		ShowMap();
	}

	this.DisplayStreet = function() {
		if (GStreetField) {
			Visible = false;
			GMapField.style.display = "none";
			ShowStreet();
		}
	}

	this.Hide = function() {
		Visible = false;
		GMapField.style.display = "none";
		if (GStreetField) {
			GStreetField.style.display = "none";
		}
	}

	this.Terminate = function() {
		// Make sure we deference all objects.
		this.Hide();
		GMapField = null;
		GStreetField = null;
		Title = null;
		Address = null;
		GLat = null;
		GLon = null;
		GLatField = null;
		GLonField = null;
		GeocodeStarted = false;
		GMapObject = null;
		Zoom = null;
		MapStyle = null;
		AddressMarker = null;

		// Dereference the functions to free their object references.
		this.Init = null;
		this.SetStreetID = null;
		this.SetLatLon = null;
		this.SetLatLonFields = null;
		this.DisplayMap = null;
		this.DisplayAerial = null;
		this.HideMap = null;
		this.Terminate = null;
	}

// Private functions
	function Geocode() {
		if (!GeocodeStarted && Address && GLatField && GLonField) {
			// We only try Geocoding once.
			GeocodeStarted = true;

			var geocoder = new GClientGeocoder();
			geocoder.getLatLng(
				Address,
				function(point) {
					if (!point) {
						alert(Address + " not found");
					} else {
						GLatField.value = point.lat();
						GLonField.value = point.lng();
						if (Visible) {
							CreateMap(point);
						}
					}
				}
			);
		}
	}
	
	function ShowMap() {
		if (GStreetField) {
			GStreetField.style.display = "none";
		}
		Visible = true;
		if (GMapObject) {
			GMapField.style.display = "block";
			GMapObject.setCenter(AddressMarker.getPoint(), Zoom, MapStyle);
		} else {
			if ((!GLat || !GLon) && GLatField && GLonField) {
				GLat = GLatField.value;	
				GLon = GLonField.value;	
			}
			if (0 == GLat || 0 == GLon) {
				Geocode();
			} else {
				CreateMap(new GLatLng(GLat, GLon));
			}
		}
	}

	function CreateMap(Point) {
		GMapField.style.display = "block";
		
		GMapObject = new GMap2(GMapField);
		GMapObject.addControl(new GLargeMapControl());
		GMapObject.addControl(new GMapTypeControl());
		GMapObject.addControl(new GScaleControl());
		GMapObject.setCenter(Point, Zoom, MapStyle);
		
		Draggable = (GLatField && GLonField);
		AddressMarker = new GMarker(Point, {draggable: Draggable});
	
		if (Draggable) {
			GEvent.addListener(AddressMarker, "dragstart",
				function() {
					GMapObject.closeInfoWindow();
				});
			
			GEvent.addListener(AddressMarker, "drag",
				function() {
					NewPoint = AddressMarker.getPoint();
					GLatField.value = NewPoint.lat();
					GLonField.value = NewPoint.lng();
				});
			
			GEvent.addListener(AddressMarker, "dragend",
				function() {
					AddressMarker.openInfoWindowHtml(Title);
				});
		}
	
		GMapObject.addOverlay(AddressMarker);
		GMapObject.openInfoWindowHtml(Point, Title);
	}

	function ShowStreet() {
		GStreetField.style.display = "block";
		if (!GStreetObject) {
			GStreetObject = new GStreetviewPanorama(GStreetField);
			GEvent.addListener(GStreetObject, "initialized",
				function() {
					GEvent.clearListeners(GStreetObject, "initialized");
					GStreetObject.show();
				});
		}
		GStreetObject.setLocationAndPOV(new GLatLng(PLat, PLon), {yaw:Bearing});
	}
}


/* calculate (initial) bearing between two points
 *   see http://williams.best.vwh.net/avform.htm#Crs
 */
function CalcBearing(lat1, lon1, lat2, lon2) {
  lat1 = lat1.toRad(); lat2 = lat2.toRad();
//  lat1 = lat1.toRad(); lat2 = toRad(lat2);
  var dLon = (lon2-lon1).toRad();

  var y = Math.sin(dLon) * Math.cos(lat2);
  var x = Math.cos(lat1)*Math.sin(lat2) -
          Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
  var bearing = Math.atan2(y, x).toBrng();
  return bearing;
}


// extend Number object with methods for converting degrees/radians

function toRad(D) {
	return D * Math.PI / 180;
}

Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
}

Number.prototype.toDeg = function() {  // convert radians to degrees (signed)
  return this * 180 / Math.PI;
}

Number.prototype.toBrng = function() {  // convert radians to degrees (as bearing: 0...360)
  return (this.toDeg()+360) % 360;
}

