// This code was derived from the help documentation located at
// http://code.google.com/apis/maps/documentation/
// It appears that you may use the code and its derivations as long as it
// meets the terms of service:
// http://code.google.com/apis/maps/faq.html#tos_commercial
// It appears that the code is available as open-source; which open-source license,
// though, is unclear.
//
// The root page is http://code.google.com/apis/maps/

// Yep, I can't use constanst in Javascript as it's not standard:
// http://www.gtalbot.org/BrowserBugsSection/MSIE7Bugs/JSConstKeyword.html

var MARKER_STARTPOINT = 0;
var MARKER_GEOCODE = 1;
var MAP_DISCOVER = 1;
var MAP_GEOCODE = 2;
var MAP_DISCOVERAUTO = 3;

var MULTIPLE_DIV_GEOCODE = "GeocodeMultiple";
var MULTIPLE_DIV_DIRECTIONS = "DirectionsMultiple";

var MULTIPLE_DIV_GEOCODE_LABEL = "GeocodeMultipleLabel";
var MULTIPLE_DIV_DIRECTIONS_LABEL = "DirectionsMultipleLabel";

var SELECT_GEOCODE = "GeocodeSelect";
var SELECT_DIRECTIONS = "DirectionsSelect";

var PUSH_PIN_BASEPATH = "/Images/Pushpins/";

var goGoogleMap;
var goGeocoder;
var goDirections;
var goOriginalDraggableGLatLng;

var gaGeocodePlaces = new Array(2);

var gcOldMultipleLocationsEntries = "";

var gaDiscoverMarkers = null;
var gaDiscoverPoints = null;
var gaDiscoverHints = null;
var gaDiscoverTexts = null;
var gaDiscoverPng = null;

var goDiscoverMarkerList = null;
var gcDiscoverMarkerListInnerHTML = "";

var goClusterer = null;

var gdMessageStartTime;
var gnSetInterval;
// By the way, it doesn't work to set and check for a variable called
// glInProcess: by
// reloading the page, all variables are reset.
// -------------------------------------------------------------------------------------------------------------------
//
function loadDiscoverAuto(tcLoadDescription, tcAction)
{
	if (tcAction == "Report")
	{
		if (tcLoadDescription == "PonticGreekFolkTales")
		{
			loadSummaryReport("Trabzon, Turkey", 39.7191, 41.0061, "TR", "Trabzon", "Trabzon", "FolkTales: Pontic Greek");
			return;
		}

		if (tcLoadDescription == "HistoricalNewYork")
		{
			loadSummaryReport("New York, NY, USA", -74.0059729, 40.7142691, "US", "NY", "New York", "Historical");
			return;
		}

		if (tcLoadDescription == "HistoricalAtlanta")
		{
			loadSummaryReport("Atlanta, GA, USA", -84.3879824, 33.7489954, "US", "GA", "Fulton", "Historical");
			return;
		}

		if (tcLoadDescription == "HistoricalGuam")
		{
			loadSummaryReport("Guam", 144.793731, 13.444304, "GU", "", "", "Historical");
			return;
		}

		if (tcLoadDescription == "HistoricalSanJuan")
		{
			loadSummaryReport("San Juan, Puerto Rico", -66.1057217, 18.4663338, "PR", "", "", "Historical");
			return;
		}

		if (tcLoadDescription == "RegulatoryNewYork")
		{
			loadSummaryReport("New York, NY, USA", -74.0059729, 40.7142691, "US", "NY", "New York", "Regulatory");
			return;
		}

		if (tcLoadDescription == "RegulatoryAustin")
		{
			loadSummaryReport("Austin, TX, USA", -97.7430608, 30.267153, "US", "TX", "Travis", "Regulatory");
			return;
		}

	}

	if (tcAction == "Map")
	{
		if (GBrowserIsCompatible())
		{
			goDiscoverMarkerList = getDiscoverMarkerListDiv();

			resizeElements(MAP_DISCOVERAUTO);

			goGoogleMap = new GMap2(getGoogleMapDiv());

			goGoogleMap.setUIToDefault();

			var loThumbView = new GOverviewMapControl(new GSize(180, 180));
			goGoogleMap.addControl(loThumbView);
			loThumbView.auto = false;

			// Center of Austin, TX
			goGoogleMap.setCenter(new GLatLng(30.268735, -97.745209), 7);

			goGeocoder = new GClientGeocoder();

			goClusterer = new Clusterer(goGoogleMap);
			goClusterer.SetMinMarkersPerCluster(21);

			var loIcon = new GIcon();
			loIcon.image = "/Images/Pushpins/cluster32.png";
			loIcon.iconSize = new GSize(71, 71);
			goClusterer.SetIcon(loIcon);

			goGoogleMap.clearOverlays();
			gaGeocodePlaces[MARKER_STARTPOINT] = null;
			gaGeocodePlaces[MARKER_GEOCODE] = null;

			if (tcLoadDescription == "PonticGreekFolkTales")
			{
				// The conversion is no longer needed as the Base Location has changed.
				// However, I'm
				// leaving the line with lcAddress as an example.
				// var lcAddress = convertHTMLCodesToCharacters("Torul,
				// U&#287;urta&#351;&#305;, Turkey");
				loadXMLPoints("Trabzon, Turkey", 39.7191, 41.0061, "TR", "Trabzon", "Trabzon", "FolkTales: Pontic Greek");
			}
			else if (tcLoadDescription == "HistoricalNewYork")
			{
				loadXMLPoints("New York, NY, USA", -74.0059729, 40.7142691, "US", "NY", "New York", "Historical");
			}
			else if (tcLoadDescription == "HistoricalAtlanta")
			{
				loadXMLPoints("Atlanta, GA, USA", -84.3879824, 33.7489954, "US", "GA", "Fulton", "Historical");
			}
			else if (tcLoadDescription == "HistoricalGuam")
			{
				loadXMLPoints("Guam", 144.793731, 13.444304, "GU", "", "", "Historical");
			}
			else if (tcLoadDescription == "HistoricalSanJuan")
			{
				loadXMLPoints("San Juan, Puerto Rico", -66.1057217, 18.4663338, "PR", "", "", "Historical");
			}
			else if (tcLoadDescription == "RegulatoryNewYork")
			{
				loadXMLPoints("New York, NY, USA", -74.0059729, 40.7142691, "US", "NY", "New York", "Regulatory");
			}
			else if (tcLoadDescription == "RegulatoryAustin")
			{
				loadXMLPoints("Austin, TX, USA", -97.7430608, 30.267153, "US", "TX", "Travis", "Regulatory");
			}
		}
		else
		{
			alert("Your browser does not support the display of Google maps.");
		}
	}

}
// -------------------------------------------------------------------------------------------------------------------
//
function loadDiscoverMap()
{
	if (GBrowserIsCompatible())
	{
		goDiscoverMarkerList = getDiscoverMarkerListDiv();

		resizeElements(MAP_DISCOVER);

		goGoogleMap = new GMap2(getGoogleMapDiv());

		goGoogleMap.setUIToDefault();

		var loThumbView = new GOverviewMapControl(new GSize(180, 180));
		goGoogleMap.addControl(loThumbView);
		loThumbView.auto = false;

		// Center of Austin, TX
		goGoogleMap.setCenter(new GLatLng(30.268735, -97.745209), 7);

		goGeocoder = new GClientGeocoder();

		goClusterer = new Clusterer(goGoogleMap);
		goClusterer.SetMinMarkersPerCluster(21);

		var loIcon = new GIcon();
		loIcon.image = "/Images/Pushpins/cluster32.png";
		loIcon.iconSize = new GSize(71, 71);
		goClusterer.SetIcon(loIcon);
	}
	else
	{
		alert("Your browser does not support the display of Google maps.");
	}

}
// -------------------------------------------------------------------------------------------------------------------
//
function loadGeocodeMap()
{
	if (GBrowserIsCompatible())
	{
		resizeElements(MAP_GEOCODE);

		goGoogleMap = new GMap2(getGoogleMapDiv());

		goGoogleMap.setUIToDefault();

		var loThumbView = new GOverviewMapControl(new GSize(180, 180));
		goGoogleMap.addControl(loThumbView);
		loThumbView.auto = false;

		// Center of Austin, TX
		goGoogleMap.setCenter(new GLatLng(30.268735, -97.745209), 7);

		goGeocoder = new GClientGeocoder();
	}
	else
	{
		alert("Your browser does not support the display of Google maps.");
	}

}
// -------------------------------------------------------------------------------------------------------------------
// discoverLocations() is called when you click on the Geocode button
// in the form. It geocodes the address entered into the form
// and adds a marker to the map at that location.
function discoverLocations()
{
	if (gaDiscoverMarkers != null)
	{
		var lnCount = gaDiscoverMarkers.length;
		for ( var i = 0; i < lnCount; ++i)
		{
			goClusterer.RemoveMarker(gaDiscoverMarkers[i]);
		}
	}

	resizeElements(MAP_DISCOVER);

	goGoogleMap.clearOverlays();
	gaGeocodePlaces[MARKER_STARTPOINT] = null;
	gaGeocodePlaces[MARKER_GEOCODE] = null;

	var loMapRouteDiv = getDirectionsDiv();

	if (!goDirections)
	{
		goDirections = new GDirections(goGoogleMap, loMapRouteDiv);

		GEvent.addListener(goDirections, "error", onDirectionsError);
		GEvent.addListener(goDirections, "addoverlay", onDirectionsAddOverlay);
	}
	else
	{
		// Just reset the directions
		goDirections.clear();
	}

	var lcGeocodeAddress = getGeocodeAddress();
	var lcDrivingFromAddress = getDrivingFromAddress();

	if (lcGeocodeAddress.length == 0)
	{
		alert("You must enter an address to discover.");
		return;
	}

	goGeocoder.getLocations(lcGeocodeAddress, setMarkersAfterDiscoverGeo);

	if (!areDirectionsRequested())
	{
		resetMultipleLocations(MARKER_STARTPOINT);
		return;
	}

	goGeocoder.getLocations(lcDrivingFromAddress, setMarkersAfterDiscoverDir);
}
// -------------------------------------------------------------------------------------------------------------------
// geocodeInformation() is called when you click on the Geocode button
// in the form. It geocodes the address entered into the form
// and adds a marker to the map at that location.
function geocodeInformation()
{
	resizeElements(MAP_GEOCODE);

	goGoogleMap.clearOverlays();
	gaGeocodePlaces[MARKER_STARTPOINT] = null;
	gaGeocodePlaces[MARKER_GEOCODE] = null;

	var loMapRouteDiv = getDirectionsDiv();

	if (!goDirections)
	{
		goDirections = new GDirections(goGoogleMap, loMapRouteDiv);

		GEvent.addListener(goDirections, "error", onDirectionsError);
		GEvent.addListener(goDirections, "addoverlay", onDirectionsAddOverlay);
	}
	else
	{
		goDirections.clear();
	}

	var lcGeocodeAddress = getGeocodeAddress();
	var lcDrivingFromAddress = getDrivingFromAddress();

	if (lcGeocodeAddress.length == 0)
	{
		alert("You must enter an address to geocode.");
		return;
	}

	goGeocoder.getLocations(lcGeocodeAddress, setMarkerAfterGeocodeGeo);

	if (!areDirectionsRequested())
	{
		resetMultipleLocations(MARKER_STARTPOINT);
		return;
	}

	goGeocoder.getLocations(lcDrivingFromAddress, setMarkerAfterGeocodeDir);
}
// -------------------------------------------------------------------------------------------------------------------
function setMarkersAfterDiscoverGeo(toResponse)
{
	setMarkersAfterDiscover(toResponse, MARKER_GEOCODE);
}
// -------------------------------------------------------------------------------------------------------------------
function setMarkersAfterDiscoverDir(toResponse)
{
	setMarkersAfterDiscover(toResponse, MARKER_STARTPOINT);
}
// -------------------------------------------------------------------------------------------------------------------
// setMarkersAfterDiscover() is called when the geocoder returns an
// answer. It adds a marker to the map with an open info window
// showing the nicely formatted version of the address and the country code.
function setMarkersAfterDiscover(toResponse, tnTrack)
{
	if (!toResponse || toResponse.Status.code != 200)
	{
		LocatingError(toResponse.Status.code);
		return;
	}

	setMultipleChoicesIfNeeded(toResponse, tnTrack, false);

	var llOneMatch = (toResponse.Placemark.length == 1);

	gaGeocodePlaces[tnTrack] = toResponse.Placemark[0];

	var loPlace = gaGeocodePlaces[tnTrack];
	var lnLongX = loPlace.Point.coordinates[0];
	var lnLatY = loPlace.Point.coordinates[1];
	var loPoint = new GLatLng(lnLatY, lnLongX);

	var lcDescription = buildDescription(loPlace);

	if (areDirectionsRequested())
	{
		var lcImage = "";
		switch (tnTrack)
		{
			case MARKER_STARTPOINT:
				lcImage = "marker_greenA.png";
				break;
			case MARKER_GEOCODE:
				lcImage = "marker_greenB.png";
				break;
		}

		var loMarker = createMarker(loPoint, loPlace.address, lcDescription, lcImage, false);
		goGoogleMap.addOverlay(loMarker);
	}

	switch (tnTrack)
	{
		case MARKER_GEOCODE:
			// Only change the address field if there's one match.
			if (llOneMatch)
			{
				setGeocodeAddress(loPlace.address);
			}

			var lcDataView = getDataView();
			if (lcDataView == "maps")
			{
				loadXMLPoints(getGeocodeAddress(), lnLongX, lnLatY, getCountryCode(loPlace), getAdministrativeArea(loPlace), getSubAdministrativeArea(loPlace), getProductSelected());
			}
			else if (lcDataView == "report")
			{
				loadSummaryReport(getGeocodeAddress(), lnLongX, lnLatY, getCountryCode(loPlace), getAdministrativeArea(loPlace), getSubAdministrativeArea(loPlace), getProductSelected());
			}
			break;

		case MARKER_STARTPOINT:
			// Only change the address field if there's one match.
			if (llOneMatch)
			{
				setDrivingFromAddress(loPlace.address);
			}

			var lcOrigin = gaGeocodePlaces[MARKER_STARTPOINT].address;
			var lcDestination = gaGeocodePlaces[MARKER_GEOCODE].address;

			goDirections.load("from: " + lcOrigin + " to: " + lcDestination);
			break;
	}

}
// -------------------------------------------------------------------------------------------------------------------
function setMarkerAfterGeocodeGeo(toResponse)
{
	setMarkerAfterGeocode(toResponse, MARKER_GEOCODE);
}
// -------------------------------------------------------------------------------------------------------------------
function setMarkerAfterGeocodeDir(toResponse)
{
	setMarkerAfterGeocode(toResponse, MARKER_STARTPOINT);
}
// -------------------------------------------------------------------------------------------------------------------
// setMarkerAfterGeocode() is called when the geocoder returns an
// answer. It adds a marker to the map with an open info window
// showing the nicely formatted version of the address and the country code.

// Here's an example for more than 1 geocoding hit:
// http://econym.org.uk/gmap/example_didyoumean2.htm
function setMarkerAfterGeocode(toResponse, tnTrack)
{
	if (!toResponse || toResponse.Status.code != 200)
	{
		LocatingError(toResponse.Status.code);
		return;
	}

	var llOneMatch = (toResponse.Placemark.length == 1);
	setMultipleChoicesIfNeeded(toResponse, tnTrack, true);

	gaGeocodePlaces[tnTrack] = toResponse.Placemark[0];

	var loPlace = gaGeocodePlaces[tnTrack];
	var lnLongX = loPlace.Point.coordinates[0];
	var lnLatY = loPlace.Point.coordinates[1];
	var loPoint = new GLatLng(lnLatY, lnLongX);

	var lcDescription = buildDescription(loPlace);

	var lcImage = "";
	if (areDirectionsRequested())
	{
		switch (tnTrack)
		{
			case MARKER_STARTPOINT:
				lcImage = "marker_greenA.png";
				break;
			case MARKER_GEOCODE:
				lcImage = "marker_greenB.png";
				break;
		}
	}

	var loMarker = createMarker(loPoint, loPlace.address, lcDescription, lcImage, lcImage.length == 0);
	goGoogleMap.addOverlay(loMarker);

	switch (tnTrack)
	{
		case MARKER_GEOCODE:
			// Only change the address field if there's one match.
			if (llOneMatch)
			{
				setGeocodeAddress(loPlace.address);
			}
			// Only center if the original geocode. If directions are calculated, then
			// the map will be reset anyway.
			goGoogleMap.setCenter(loPoint, 14);
			break;

		case MARKER_STARTPOINT:
			// Only change the address field if there's one match.
			if (llOneMatch)
			{
				setDrivingFromAddress(loPlace.address);
			}

			var lcOrigin = gaGeocodePlaces[MARKER_STARTPOINT].address;
			var lcDestination = gaGeocodePlaces[MARKER_GEOCODE].address;

			goDirections.load("from: " + lcOrigin + " to: " + lcDestination);
			break;
	}
}
// -------------------------------------------------------------------------------------------------------------------
function resetMultipleLocations(tnType)
{
	switch (tnType)
	{
		case MARKER_GEOCODE:
			document.getElementById(MULTIPLE_DIV_GEOCODE).innerHTML = "";
			document.getElementById(MULTIPLE_DIV_GEOCODE_LABEL).innerHTML = "";
			break;

		case MARKER_STARTPOINT:
			document.getElementById(MULTIPLE_DIV_DIRECTIONS).innerHTML = "";
			document.getElementById(MULTIPLE_DIV_DIRECTIONS_LABEL).innerHTML = "";
			break;
	}
}
// -------------------------------------------------------------------------------------------------------------------
// From http://www.w3schools.com/XML/tryit.asp?filename=tryxml_httprequest_js1
// Now I'm just using Google's API.
function OnProductSelection()
{
	var lcProduct = getProductSelected();

	var lcFilePath = buildJSInterfaceString("ProductListInfo", "&Type=ProductAddressHTML&Product=" + getProductSelected());

	var loXMLHttpProductSelection = GXmlHttp.create();

	loXMLHttpProductSelection.open("GET", lcFilePath, true);

	loXMLHttpProductSelection.onreadystatechange = function()
	{
		// 4 = "loaded"
		if (loXMLHttpProductSelection.readyState == 4)
		{
			// 200 = "OK"
			if (loXMLHttpProductSelection.status == 200)
			{
				var lcResults = allTrim(loXMLHttpProductSelection.responseText);
				// By the way, it's readOnly and not readonly: only readOnly works.
				if (lcResults.length > 0)
				{
					setGeocodeAddress(convertHTMLCodesToCharacters(lcResults));
					window.document.forms[0].geocodeaddress.readOnly = true;
				}
				else
				{
					window.document.forms[0].geocodeaddress.readOnly = false;
				}
			}
			else
			{
				alert("Problem retrieving data:" + loXMLHttpProductSelection.statusText);
			}
		}
	}

	loXMLHttpProductSelection.send(null);
}
// -------------------------------------------------------------------------------------------------------------------
function OnMultipleLocations(toList, tlAutoResubmit)
{
	var lcName = toList.id;
	var lnIndex = toList.selectedIndex;
	var lcValue = toList.options[lnIndex].value;

	// Internet Explorer apparently doesn't like value, only text.
	if ((!lcValue) || (lcValue.length == 0))
	{
		lcValue = toList.options[lnIndex].text;
	}

	if (lcName == SELECT_GEOCODE)
	{
		setGeocodeAddress(lcValue);
	}
	else if (lcName == SELECT_DIRECTIONS)
	{
		setDrivingFromAddress(lcValue);
	}

	// Do not call onsubmit() if there have been no changes. Otherwise, if you get
	// any alert error messages, like the unknown error or directions couldn't be
	// determined, you could go into an infinite loop of error messages.
	var lcOldMultipleLocationsEntries = getGeocodeAddress() + getDrivingFromAddress();
	if (gcOldMultipleLocationsEntries.toLowerCase() == lcOldMultipleLocationsEntries.toLowerCase())
	{
		return;
	}

	gcOldMultipleLocationsEntries = lcOldMultipleLocationsEntries;

	if (tlAutoResubmit)
	{
		document.forms["address_entry"].onsubmit();
	}
}
// -------------------------------------------------------------------------------------------------------------------
function isMultipleChoicesStillBeingUsed(tnType)
{
	var llStillUsed = false;
	var lcAddress = "";
	var loSelectList = null;

	switch (tnType)
	{
		case MARKER_GEOCODE:
			lcAddress = getGeocodeAddress().toLowerCase();
			loSelectList = document.getElementById(SELECT_GEOCODE);
			break;

		case MARKER_STARTPOINT:
			lcAddress = getDrivingFromAddress().toLowerCase();
			loSelectList = document.getElementById(SELECT_DIRECTIONS);
			break;

		default:
			alert("Invalid choice for " + isMultipleChoicesStillBeingUsed + ": " + tnType);
			return (llStillUsed);
	}

	if (loSelectList)
	{
		for ( var i = 0; i < loSelectList.options.length; i++)
		{
			if (lcAddress == loSelectList.options[i].text.toLowerCase())
			{
				llStillUsed = true;
				break;
			}
		}
	}

	return (llStillUsed);
}
// -------------------------------------------------------------------------------------------------------------------
function setMultipleChoicesIfNeeded(toResponse, tnType, tlAutoResubmit)
{
	if (isMultipleChoicesStillBeingUsed(tnType))
	{
		return;
	}

	var lnCount = toResponse.Placemark.length;
	if (lnCount == 1)
	{
		resetMultipleLocations(tnType);
		return;
	}

	var lcSelect = "<select id=\"";
	var lcAddress = "";

	switch (tnType)
	{
		case MARKER_GEOCODE:
			lcSelect += SELECT_GEOCODE;
			lcAddress = getGeocodeAddress();
			break;

		case MARKER_STARTPOINT:
			lcSelect += SELECT_DIRECTIONS;
			lcAddress = getDrivingFromAddress();
			break;
	}

	lcSelect += "\" size=\"1\" onfocus=\"OnMultipleLocations(this," + tlAutoResubmit + ");\" onchange=\"OnMultipleLocations(this," + tlAutoResubmit + ");\">\n";

	for ( var i = 0; i < lnCount; i++)
	{
		lcSelect += "<option>" + allTrim(toResponse.Placemark[i].address) + "</option>\n";
	}

	lcSelect += "</select>\n";

	var lcLabel = "<i>Multiple hits for " + lcAddress + "</i>";
	switch (tnType)
	{
		case MARKER_GEOCODE:
			document.getElementById(MULTIPLE_DIV_GEOCODE_LABEL).innerHTML = lcLabel;
			document.getElementById(MULTIPLE_DIV_GEOCODE).innerHTML = lcSelect;

			document.getElementById(SELECT_GEOCODE).focus();
			break;

		case MARKER_STARTPOINT:
			document.getElementById(MULTIPLE_DIV_DIRECTIONS_LABEL).innerHTML = lcLabel;
			document.getElementById(MULTIPLE_DIV_DIRECTIONS).innerHTML = lcSelect;

			document.getElementById(SELECT_DIRECTIONS).focus();
			break;
	}

}
// -------------------------------------------------------------------------------------------------------------------
function loadSummaryReport(tcBaseLocation, tnLongitudeX, tnLatitudeX, tcIsoAlpha, tcAdmin, tcSubAdmin, tcProductSelected)
{
	var lcHTTPPath = buildJSInterfaceString("SummaryReport", "&LongX=" + tnLongitudeX + "&LatY=" + tnLatitudeX + "&Address=" + tcBaseLocation + "&Product=" + tcProductSelected + "&IsoAlpha="
			+ tcIsoAlpha + "&Admin=" + tcAdmin + "&SubAdmin=" + tcSubAdmin);

	// If you use window.open rather than window.location.href = lcHTTPPath
	// then the Back button will work by returning you to the map screen.
	window.open(lcHTTPPath, "_self");
}
// -------------------------------------------------------------------------------------------------------------------
// By the way, http://www.hunlock.com/blogs/Mastering_Javascript_Arrays
// recommends initializing arrays with [] rather than new Array();
function loadXMLPoints(tcBaseLocation, tnLongitudeX, tnLatitudeX, tcIsoAlpha, tcAdmin, tcSubAdmin, tcProductSelected)
{
	var lcXMLPath = buildJSInterfaceString("GetLocationsXML", "&LongX=" + tnLongitudeX + "&LatY=" + tnLatitudeX + "&Address=" + tcBaseLocation + "&Product=" + tcProductSelected + "&IsoAlpha="
			+ tcIsoAlpha + "&Admin=" + tcAdmin + "&SubAdmin=" + tcSubAdmin);

	startAsynchronousMessageUpdate("Querying data points for area around " + tcBaseLocation);

	GDownloadUrl(lcXMLPath, function(toData, toResponseCode)
	{
		// Cause it won't update at this point anyway.
			stopAsynchronousMessageUpdate();

			var loXML = GXml.parse(toData);

			var loAddresses = loXML.documentElement.getElementsByTagName("site");
			var lnAddresses = loAddresses.length;

			var lcHint;
			var loPoint = null;
			var lcText;
			var llNewPoint = false;
			var lnZoom;
			var llShowBase = true;

			gaDiscoverPoints = [];
			gaDiscoverHints = [];
			gaDiscoverTexts = [];
			gaDiscoverPng = [];

			var llFirstMarker = false;
			var llStartTextPoint = true;

			var laLabels = [];
			var laAcronyms = [];
			var laPrimaryKeys = [];

			for ( var i = 0; i < lnAddresses; i++)
			{
				if (llStartTextPoint)
				{
					var laLabels = [];
					var laAcronyms = [];
					var laPrimaryKeys = [];

					llStartTextPoint = false;
				}

				var lcPushpinID = loAddresses[i].getAttribute("pushpinid");
				// FYI: there are some attributes specific to Base Location and not
			// the rest of the rows, and vice-versa.
			// By the way, I'm shortening all of the attribute names in an effort to
			// reduce the data traffic.
			llFirstMarker = (i == 0);

			if (!llNewPoint)
			{
				// FYI: there are some attributes specific to Base Location and not
				// the rest of the rows, and vice-versa.
				// By the way, I'm shortening all of the attribute names in an effort to
				// reduce the data traffic.
				var lcX = loAddresses[i].getAttribute("longx");
				var lcY = loAddresses[i].getAttribute("laty");
				var lcAddress = loAddresses[i].getAttribute("addr");

				loPoint = new GLatLng(parseFloat(lcY), parseFloat(lcX));

				if (!llFirstMarker)
				{
					var lcMiles = loAddresses[i].getAttribute("miles");
					var lcAzi = loAddresses[i].getAttribute("azi");

					lcText = "Pushpin ID #" + lcPushpinID + "<br><i>" + lcAddress + "</i><br><i>" + lcMiles + " mi (" + lcAzi + ") from Base Location</i><br><br>";
					lcHint = convertHTMLCodesToCharacters(lcAddress);
				}
				// Base Location
				else
				{
					lcText = "Base Location<br><i>" + lcAddress + "</i><br><br>";

					var lnZoom = parseInt(loAddresses[i].getAttribute("zoom"));
					llShowBase = (loAddresses[i].getAttribute("show") == "true");

					if (llShowBase)
					{
						lcHint = "Base Location";
					}
					else
					{
						lcHint = convertHTMLCodesToCharacters(lcAddress) + " (Base Location)";
					}

					goGoogleMap.setCenter(loPoint, lnZoom);
				}

				llNewPoint = true;
			}

			if (!llFirstMarker)
			{
				laLabels.push(loAddresses[i].getAttribute("lbl"));
				laAcronyms.push(loAddresses[i].getAttribute("acrnm"));
				laPrimaryKeys.push(loAddresses[i].getAttribute("key"));
			}

			if ((i == (lnAddresses - 1)) || (lcPushpinID != loAddresses[i + 1].getAttribute("pushpinid")))
			{
				llNewPoint = false;
				llStartTextPoint = true;

				var lnElements = laLabels.length;
				var lnLimit = 20;
				var lnColumns = Math.floor(lnElements / lnLimit) + 1;

				lcText += "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">";

				for ( var r = 0; r < lnLimit; ++r)
				{
					lcText += "<tr>";
					for ( var c = 0; c < lnColumns; ++c)
					{
						var lnIndex = (c * lnLimit) + r;
						if (lnIndex >= lnElements)
						{
							continue;
						}

						lcText += "<td style=\"padding-right: 8px; white-space:nowrap;\">";

						var lcLabel = laLabels[lnIndex];
						if ((lnColumns > 1) && (lcLabel.length > 36))
						{
							lcLabel = lcLabel.substring(0, 36) + "...";
						}
						var lcAcronym = laAcronyms[lnIndex];
						var lcPrimaryKey = laPrimaryKeys[lnIndex];

						var lcHref = buildJSInterfaceString("DetailReport", "&Key=" + lcPrimaryKey + "&Acrnm=" + lcAcronym)

						lcText += "<a href=\"javascript:void(0)\" onclick=displayWebPageInDiv(\"" + lcHref + "\")>" + lcLabel + "</a><br>";

						lcText += "</td>";
					}
					lcText += "</tr>";
				}

				lcText += "</table>";

				gaDiscoverPoints.push(loPoint);
				gaDiscoverHints.push(lcHint);
				gaDiscoverTexts.push("<p>" + lcText + "</p>");

				if ((lcPushpinID == "0") && (llShowBase))
				{
					gaDiscoverPng.push("baselocation32.png");
				}
				else
				{
					gaDiscoverPng.push("");
				}
			}
		}

		displayPoints();
	});

}
// -------------------------------------------------------------------------------------------------------------------
function startAsynchronousMessageUpdate(tcMessage)
{
	gdMessageStartTime = new Date();
	gnSetInterval = window.setInterval("messageWindow(\"" + tcMessage + "\")", 500);
}
// -------------------------------------------------------------------------------------------------------------------
function stopAsynchronousMessageUpdate()
{
	window.clearInterval(gnSetInterval);
}
// -------------------------------------------------------------------------------------------------------------------
// I wanted a function that would update the points as they are created, not
// wait till completion.
// And this way, the messageWindow would also work.
function displayPoints()
{
	var lnPoints = gaDiscoverPoints.length;
	if ((lnPoints != gaDiscoverHints.length) || (lnPoints != gaDiscoverTexts.length))
	{
		alert("The arrays are not equal in displayPoints.");
		return;
	}

	gaDiscoverMarkers = [];

	gcDiscoverMarkerListInnerHTML = "";
	goDiscoverMarkerList.innerHTML = "";

	for ( var i = 0; i < lnPoints; ++i)
	{
		var loMarker = createMarker(gaDiscoverPoints[i], gaDiscoverHints[i], gaDiscoverTexts[i], gaDiscoverPng[i], false);
		gaDiscoverMarkers.push(loMarker);

		addDiscoverEvents(i, gaDiscoverHints[i]);
		// Instead of using goGoogleMap.addOverlay(loMarker)
		// I'm now using goClusterer.AddMarker which makes displaying points
		// about 7 to 9 times faster.
		goClusterer.AddMarker(loMarker, gaDiscoverHints[i]);
	}

	goDiscoverMarkerList.innerHTML = " <div><ul>\n" + gcDiscoverMarkerListInnerHTML + "</ul></div>";

	// Not needed so just clear.
	gaDiscoverPoints = [];
	gaDiscoverHints = [];
	gaDiscoverTexts = [];

	messageWindow("Generated " + lnPoints + " unique location points. . . .");

	fadeAwayWaitWindow(100, -1);
}
// -------------------------------------------------------------------------------------------------------------------
// This function should be called by a timer: otherwise it will never refresh
// for multiple calls.
function fadeAwayWaitWindow(tnOpacity, tnStep)
{
	var loDiv = getMessageWindowElement();
	var loShadow = getMessageWindowShadowElement();

	setOpacity(loDiv, tnOpacity);
	setOpacity(loShadow, tnOpacity / 2);

	if (tnOpacity > 0)
	{
		window.setTimeout("fadeAwayWaitWindow(" + (tnOpacity + tnStep) + ", " + tnStep + ")", (tnOpacity < 100) ? 10 : 1000);
	}
	else
	{
		loDiv.style.visibility = "hidden";
		loShadow.style.visibility = "hidden";
	}
}
// -------------------------------------------------------------------------------------------------------------------
// This function should be called by a timer: otherwise it will never refresh
// for multiple calls.
function messageWindow(tcMessage)
{
	var loMessage = getMessageWindowElement();
	var loShadow = getMessageWindowShadowElement();

	setOpacity(loMessage, 100);
	setOpacity(loShadow, 50);

	loMessage.style.visibility = "visible";
	loShadow.style.visibility = "visible";

	var lcMessage = tcMessage;

	var loDate = new Date();
	var lnSeconds = Math.ceil((loDate.getTime() - gdMessageStartTime.getTime()) / 1000.0);
	lcMessage += "<br>" + lnSeconds + " second(s)";

	// Set to the color of white in the <p></p> statement.
	loMessage.innerHTML = "<p style=\"color:white\">&nbsp;&nbsp;" + lcMessage + "&nbsp;&nbsp;</p><p>&nbsp;</p>";
}
// -------------------------------------------------------------------------------------------------------------------
function getMessageWindowElement()
{
	var lcDivID = "MessageWindowWebPageInDiv";
	var loDiv = document.getElementById(lcDivID);

	if (!loDiv)
	{
		loDiv = document.createElement("div");
		loDiv.id = lcDivID;
		document.body.appendChild(loDiv);

		loDiv.style.visibility = "hidden";
		loDiv.style.top = "10%";
		loDiv.style.left = "25%";
		loDiv.style.position = "absolute";
		loDiv.style.zIndex = 999;
		loDiv.style.width = "50%";
		loDiv.style.height = "10%";
		loDiv.style.backgroundColor = "#001900";
		loDiv.style.border = "0px";
		loDiv.style.verticalAlign = "middle";

		loDiv.align = "center";
	}

	return (loDiv);
}
// -------------------------------------------------------------------------------------------------------------------
function getMessageWindowShadowElement()
{
	var lcDivID = "MessageWindowShadowWebPageInDiv";
	var loDiv = document.getElementById(lcDivID);

	if (!loDiv)
	{
		loDiv = document.createElement("div");
		loDiv.id = lcDivID;
		document.body.appendChild(loDiv);

		loDiv.style.visibility = "hidden";
		loDiv.style.top = "12%";
		loDiv.style.left = "26%";
		loDiv.style.position = "absolute";
		loDiv.style.zIndex = 998;
		loDiv.style.width = "50%";
		loDiv.style.height = "10%";
		loDiv.style.backgroundColor = "black";
		loDiv.style.border = "0px";
		loDiv.style.verticalAlign = "middle";

		loDiv.align = "center";
	}

	return (loDiv);
}
// -------------------------------------------------------------------------------------------------------------------
// This function adds the links and listeners for the discover screen.
function addDiscoverEvents(tnDiscoverMarker, tcHint)
{
	var loMarker = gaDiscoverMarkers[tnDiscoverMarker]

	// By the way, trying to find the style element from CSS using
	// document.styleSheets is convuluted.
	// http://www.quirksmode.org/dom/changess.html
	GEvent.addListener(loMarker, "mouseover", function()
	{
		loMarker.setImage(PUSH_PIN_BASEPATH + "markergreen.png");

		goDiscoverMarkerList.getElementsByTagName("a")[tnDiscoverMarker].style.textDecoration = "underline";
		goDiscoverMarkerList.getElementsByTagName("a")[tnDiscoverMarker].style.color = "rgb(0, 196, 0)";
	});
	GEvent.addListener(loMarker, "mouseout", function()
	{
		if (gaDiscoverPng[tnDiscoverMarker].length > 0)
		{
			loMarker.setImage(PUSH_PIN_BASEPATH + gaDiscoverPng[tnDiscoverMarker]);
		}
		else
		{
			loMarker.setImage(PUSH_PIN_BASEPATH + "marker.png");
		}

		goDiscoverMarkerList.getElementsByTagName("a")[tnDiscoverMarker].style.textDecoration = "none";
		goDiscoverMarkerList.getElementsByTagName("a")[tnDiscoverMarker].style.color = "rgb(0, 0, 196)";
		;
	});

	var lcLinkLabel = tcHint;
	if (lcLinkLabel.length > 35)
	{
		lcLinkLabel = lcLinkLabel.substring(0, 35) + "...";
	}

	// If you don't have an href, then the hand pointer doesn't appear when
	// hovering.
	gcDiscoverMarkerListInnerHTML += "<li><a href=\"JavaScript:void(0);\" onclick='GEvent.trigger(gaDiscoverMarkers[" + tnDiscoverMarker + "],\"click\")' onmouseover='GEvent.trigger(gaDiscoverMarkers["
			+ tnDiscoverMarker + "],\"mouseover\")' onmouseout='GEvent.trigger(gaDiscoverMarkers[" + tnDiscoverMarker + "],\"mouseout\")' >" + lcLinkLabel + "</a></li>";
}
// -------------------------------------------------------------------------------------------------------------------
// This function creates a balloon push pin on the map.
// I got the icons from places like
// http://maps.google.com/intl/en_us/mapfiles/marker_greenA.png
// http://maps.google.com/intl/en_us/mapfiles/marker_greenB.png
// http://maps.google.com/intl/en_us/mapfiles/marker.png
function createMarker(toPoint, tcTextTip, tcInformation, tcImage, tlDraggable)
{
	var loCreateMarker;

	var loIcon = new GIcon(G_DEFAULT_ICON);

	if (tcImage.length > 0)
	{
		loIcon.image = PUSH_PIN_BASEPATH + tcImage;
	}

	goOriginalDraggableGLatLng = null;
	if (!tlDraggable)
	{
		loCreateMarker = new GMarker(toPoint,
		{
			title :tcTextTip,
			icon :loIcon
		});
	}
	else
	{
		loCreateMarker = new GMarker(toPoint,
		{
			title :tcTextTip,
			icon :loIcon,
			draggable :true
		});

		goOriginalDraggableGLatLng = loCreateMarker.getLatLng();
	}

	GEvent.addListener(loCreateMarker, "click", function()
	{
		// If you use loCreateMarker.openInfoWindowHtml, then only the maxWidth
			// option is available.
			// So just use the GMap2 object openInfoWindowHtml. Now the popup won't
			// disappear when scrolling on the map. The user has to close the window.
			goGoogleMap.openInfoWindowHtml(loCreateMarker.getLatLng(), determineInformationText(loCreateMarker, tcInformation),
			{
				noCloseOnClick :true
			});
		});

	if (loCreateMarker.draggable())
	{
		GEvent.addListener(loCreateMarker, "dragend", function()
		{
			// I want the default behaviour of noCloseOnClick for any dragging
				// balloon: it behaves better for dragging.
				loCreateMarker.openInfoWindowHtml(determineInformationText(loCreateMarker, tcInformation));
			});
	}

	return (loCreateMarker);
}
// -------------------------------------------------------------------------------------------------------------------
function determineInformationText(toCreateMarker, tcInformation)
{
	var lcInformation = tcInformation;

	if (goOriginalDraggableGLatLng == null)
	{
		return (lcInformation);
	}

	if (goOriginalDraggableGLatLng == toCreateMarker.getLatLng())
	{
		return (lcInformation);
	}

	lcInformation += "<p>Adjusted Longitude (X):&nbsp;<b>" + toCreateMarker.getLatLng().lng().toFixed(6) + "</b><br>Adjusted Latitude (Y):&nbsp;<b>" + toCreateMarker.getLatLng().lat().toFixed(6)
			+ "</b><br>Distance from Original (meters):&nbsp;<b>" + toCreateMarker.getLatLng().distanceFrom(goOriginalDraggableGLatLng).toFixed(6) + "</b></p>";

	return (lcInformation);
}
// -------------------------------------------------------------------------------------------------------------------
// By the way, using tables and setting td width="25%" does not appear to work
// as the empty cell will not size to 25%. And the maps will not resize the
// cells: they just try to squeeze into the available space. Thus the below
// routine that uses actual pixel size.
function resizeElements(tnType)
{
	var loGoogleMapDiv = getGoogleMapDiv();
	var loDirectionsDiv = getDirectionsDiv();

	var lnHeight = window.document.body.clientHeight;
	var lnWidth = window.document.body.clientWidth;
	var llOkay = true;

	switch (tnType)
	{
		// Discover map
		case MAP_DISCOVER:
			var loCopyrightCell = getCopyrightCell();

			loGoogleMapDiv.style.width = Math.ceil(lnWidth * (areDirectionsRequested() ? 0.60 : 0.84)) + "px";
			loGoogleMapDiv.style.height = Math.ceil(lnHeight * 0.75) + "px";

			goDiscoverMarkerList.style.width = Math.ceil(lnWidth * 0.15) + "px";
			goDiscoverMarkerList.style.height = Math.ceil(lnHeight * 0.75) + "px";

			var lnCopyRightHeight = Math.ceil(lnHeight * 0.10);

			loDirectionsDiv.style.width = Math.ceil(lnWidth * (areDirectionsRequested() ? 0.25 : 0.01)) + "px";
			loDirectionsDiv.style.height = Math.ceil((lnHeight * 0.75) + lnCopyRightHeight) + "px";

			loCopyrightCell.height = lnCopyRightHeight + "px";
			break;

		// Geocoding map
		case MAP_GEOCODE:
			var loCopyrightCell = getCopyrightCell();

			loGoogleMapDiv.style.width = Math.ceil(lnWidth * (areDirectionsRequested() ? 0.71 : 0.95)) + "px";
			loGoogleMapDiv.style.height = Math.ceil(lnHeight * 0.75) + "px";

			var lnCopyRightHeight = Math.ceil(lnHeight * 0.10);

			loDirectionsDiv.style.width = Math.ceil(lnWidth * (areDirectionsRequested() ? 0.25 : 0.01)) + "px";
			loDirectionsDiv.style.height = Math.ceil((lnHeight * 0.75) + lnCopyRightHeight) + "px";

			loCopyrightCell.height = lnCopyRightHeight + "px";
			break;

		// Discover Auto map
		case MAP_DISCOVERAUTO:
			loGoogleMapDiv.style.width = Math.ceil(lnWidth * 0.83) + "px";
			loGoogleMapDiv.style.height = Math.ceil(lnHeight * 0.90) + "px";

			goDiscoverMarkerList.style.width = Math.ceil(lnWidth * 0.15) + "px";
			goDiscoverMarkerList.style.height = Math.ceil(lnHeight * 0.90) + "px";
			break;

		default:
			llOkay = false;
			alert(tnType + " is not a recognized type for " + resizeDiv);
			break;
	}

	if ((!llOkay) || (!goGoogleMap))
	{
		return;
	}

	var loCenter = goGoogleMap.getCenter();
	var loZoom = goGoogleMap.getZoom();
	goGoogleMap.checkResize();
	goGoogleMap.setCenter(loCenter, loZoom);
}
// -------------------------------------------------------------------------------------------------------------------
function getProductSelected()
{
	var lnIndex = window.document.forms[0].productlist.selectedIndex;
	var lcValue = window.document.forms[0].productlist.options[lnIndex].value;

	// Internet Explorer apparently doesn't like value, only text.
	if ((!lcValue) || (lcValue.length == 0))
	{
		lcValue = window.document.forms[0].productlist.options[lnIndex].text;
	}

	return (allTrim(lcValue));
}
// -------------------------------------------------------------------------------------------------------------------
function areDirectionsRequested()
{
	var lcDrivingFromAddress = getDrivingFromAddress();

	return (lcDrivingFromAddress.length > 0);
}
// -------------------------------------------------------------------------------------------------------------------
function setGeocodeAddress(tcAddress)
{
	window.document.forms[0].geocodeaddress.value = tcAddress;
}
// -------------------------------------------------------------------------------------------------------------------
function getGeocodeAddress()
{
	return (allTrim(window.document.forms[0].geocodeaddress.value));
}
// -------------------------------------------------------------------------------------------------------------------
function setDrivingFromAddress(tcAddress)
{
	window.document.forms[0].drivingfromaddress.value = tcAddress;
}
// -------------------------------------------------------------------------------------------------------------------
function getDrivingFromAddress()
{
	return (allTrim(window.document.forms[0].drivingfromaddress.value));
}
// -------------------------------------------------------------------------------------------------------------------
function getDataView()
{
	var loDataView = window.document.forms[0].dataview;
	var lnLength = loDataView.length;

	for ( var i = 0; i < lnLength; i++)
	{
		if (loDataView[i].checked)
		{
			return (loDataView[i].value);
		}
	}

	return ("");
}
// -------------------------------------------------------------------------------------------------------------------
function getCopyrightCell()
{
	return (window.document.getElementById("CellCopyright"));
}
// -------------------------------------------------------------------------------------------------------------------
function getGoogleMapDiv()
{
	return (window.document.getElementById("DivGoogleMap"));
}
// -------------------------------------------------------------------------------------------------------------------
function getDirectionsDiv()
{
	return (window.document.getElementById("DivMapRoute"));
}
// -------------------------------------------------------------------------------------------------------------------
// This Div is not in Geocode.htm
function getDiscoverMarkerListDiv()
{
	var loDiv = window.document.getElementById("DivDiscoverMarkerList");

	if (!loDiv)
	{
		alert("Unable to find DivDiscoverMarkerList.");
	}

	return (loDiv);
}
// -------------------------------------------------------------------------------------------------------------------
// Turns out that the last marker is the one that is covering the original
// destination marker.
function onDirectionsAddOverlay()
{
	var lnMarkers = goDirections.getNumGeocodes();
	for ( var i = 0; i < lnMarkers; i++)
	{
		var loMarker = goDirections.getMarker(i);
		if (loMarker != null)
		{
			loMarker.hide();
		}
	}
}
// -------------------------------------------------------------------------------------------------------------------
function onDirectionsError()
{
	LocatingError(goDirections.getStatus().code);
}
// -------------------------------------------------------------------------------------------------------------------

