/*!
 * jKMLMap is a flexible, easy-to-use Javascript library for generating Google Maps from KML files.
 * For features, instructions, licensing details, and a demo, please visit the project homepage.
 * 
 * Homepage:  http://www.justin-klein.com/projects/projectjkmlmap
 * License:   http://creativecommons.org/licenses/by-sa/2.5 (for personal use)
 * Version:   1.1.0
 * Copyright: (c) 2010 Justin Klein
 * 
 * 
 * Change Log:
 *
 * 1.1.2 - xx/xx/20xx
 * -Fix XMLHttpRequest error detection for Opera
 * -Fix bug when setting the view from a path (was still using old APIv2 call)
 * -Allow any whitespace as vertex delimiters for paths (required for kmls created via MyMaps on maps.google.com)
 * -Default to Hybrid map type
 * 
 * 1.1.1 - 08/31/2011
 * -Fix setting the proper zoom
 * 
 * 1.1.0 - 08/26/2011
 * -Updated to GMaps API v3.
 * 
 * 1.0.1 - 03/02/2010
 * -Fixed a bug setting CSS classes for markers with links when all markers used the same icon 
 * -Changed some poorly named CSS classes
 * -Abort when AJAX returns any status other than 200 (not just 404)
 * -Handle markers that contain no coordinates
 * -Add new CSS class for EMPTY folders, so they can be styled differently
 * -Fixed the positioning of the "Created by jKMLMap" text
 * -Add demo HTML to distribute with this file
 * 
 * 1.0.0 - 02/28/2010
 * -First Release
 */


///////////////////////////////////////////////////////////////////////
//Initialize a jKMLMap.  For detailed instructions, see the project homepage (above).  opts can contain:
//   mapDiv ->   The ID of the div where the map will be output; defaults to "jKMLMap"
//   menuDiv->   The ID of the div where the menu will be output; defaults to no menu
//   mkrBasic->  A MarkerImage used for the map's marker icons.  If null, a default icon will be used.
//   mkrLinks->  A MarkerImage that can be used to differentiate markers whose descriptions contain a hyperlink.  If null, a default icon will be used.
//   mkrSpecial->A MarkerImage that can be applied to markers with specific text in their descriptions...
//   mkrSpecialRegex->A regular expression used to match the text that will trigger a mkrSpecial
///////////////////////////////////////////////////////////////////////
function jKMLMap( opts )
{
	//Parse user options
	var isset 			= function(x){return (typeof(x) != "undefined");};
	if( !isset(opts) ) var opts = {};
	var mapdiv			= isset(opts.mapDiv)?opts.mapDiv:'jKMLMap';
	var menudiv			= isset(opts.menuDiv)?document.getElementById(opts.menuDiv):null;
	var mkrBasic	    = isset(opts.mkrBasic)?opts.mkrBasic:null;
	var mkrLinks	    = isset(opts.mkrLinks)?opts.mkrLinks:null;
	var mkrSpecial      = isset(opts.mkrSpecial)?opts.mkrSpecial:null;
	var mkrSpecialRegex = isset(opts.mkrSpecialRegex)?opts.mkrSpecialRegex:/Current Location/;
	var isJustin		= isset(opts.isJustin);
	
	//Error check the mapdiv
	mapdiv = document.getElementById(mapdiv);
	if( mapdiv == null )
	{
		alert("jKMLMap Error: The mapdiv you specified could not be found.");
		return;
	}
			
	//Setup some private member vars
	var _self			= this;
	var xmlDocRoot 		= null;
	var elmntStack		= null;
	var countStack		= null;
	var markers			= null;
	var MTypes 		    = { FOLDER:1, POINT:2, POLYLINE:3 };
	var menuIndex		= 0;
	var lastPercentage	= 0;
	var viewWasSet      = false;
	var openedInfoWindow= null;
	
	//Optional callbacks registered by RegisterCallback()
	var callback_finished 	= null;
	var callback_progress 	= null;
	var callback_start 		= null;
		
	//Create a subdiv inside the mapdiv for the Google Map...
	var mapInner = document.createElement("div");
	mapInner.setAttribute('id', "jkml_map_inner");
	mapInner.setAttribute('style', "width:inherit;height:inherit;");
	if( document.all )	mapInner.style.setAttribute('cssText', "width:100%;height:100%;"); //For stupid IE
	mapdiv.appendChild(mapInner);
	
	//Create the map
	var mapCenter = new google.maps.LatLng(9.5, -164);
	var mapOptions = { center: mapCenter, zoom: 2, mapTypeId: google.maps.MapTypeId.HYBRID };
	this.map = new google.maps.Map(mapInner, mapOptions);
	
	//And add my disclaimer text
	var disclaimer = document.createElement("div");
	disclaimer.setAttribute("style", "text-align:right;width:100%;");
	disclaimer.innerHTML = '<small>Created by <a href="http://www.justin-klein.com/projects/projectjkmlmap">jKMLMap</a></small>';
	mapdiv.appendChild(disclaimer);

	//Handy for debugging positions
	if(isset(opts.debug))
	{
		google.maps.event.addListener(_self.map, "bounds_changed", function()
		{console.log( "Map At " + _self.map.getCenter() + ", Zoom " + _self.map.getZoom());});
	}
	
	
	///////////////////////////////////////////////////////////////////////
	//Optionally register some callbacks to receive notification when an event occurs
	//Available: onStart(void), onComplete(void), onProgress(msg,percent)
	///////////////////////////////////////////////////////////////////////
	this.RegisterCallback = function(type, func)
	{
		if( type == 'onComplete' ) 		callback_finished = func;
		else if( type == 'onProgress' )	callback_progress = func;
		else if( type == 'onStart' )	callback_start = func;
		else							alert("jKMLMap Error: RegisterCallback does not know about callback '"+type+"'.");
	};
	
	
	///////////////////////////////////////////////////////////////////////
	//Load a map given its kml filename
	///////////////////////////////////////////////////////////////////////
	this.LoadMap = function( kmlFile )
	{
		//Clear markers and map
		if( markers != null) 
		{
			for( var i = 0; i < markers.length; i++ )
			{
				if( markers[i].type == MTypes.POINT || markers[i].type == MTypes.POLYLINE )
					markers[i].setMap(null);
			}
		}
		viewWasSet = false;
		markers = [];
	
		//Start loading the KML Data
		if( callback_start != null ) callback_start();
		set_status( "<strong>Status:</strong> Requesting KML Data...");
		request_data( kmlFile );
	};

	//***THE USER SHOULD NOT NEED TO MANUALLY CALL ANYTHING BELOW HERE***********//
	//***THE USER SHOULD NOT NEED TO MANUALLY CALL ANYTHING BELOW HERE***********//
	//***THE USER SHOULD NOT NEED TO MANUALLY CALL ANYTHING BELOW HERE***********//
	
	//////////////////////////////////////////////////////////////////////
	//Called by LoadMap(): Begin asynchronously downloading the kmldata and call load_finish when done.
	var request_data = function( kmlfile )
	{
		if (window.ActiveXObject)
		{	// code for IE.
			// First use an Microsoft.XMLHTTP object to find out the filesize by doing a header request
			var headReq	= new ActiveXObject("Microsoft.XMLHTTP");
			var filesize=99999999;
			headReq.onreadystatechange = function () {	if( headReq.readyState == 4 ) filesize = headReq.getResponseHeader("Content-Length"); }
			headReq.open("HEAD",kmlfile,true);
			headReq.send(null);
			
			//Now request the actual data using a DOMDocument (because XMLHTTP doesn't allow access to partially downloaded data, so I couldn't calculate the progress by GETing the file that way)
			//(Actually, xmldoc.text.length is NOT the filesize, but I spent forever trying to figure out how to make the progress work in stupid IE, so this'll have to do for now.)
			var xmlDoc 	= new ActiveXObject("MSXML2.DOMDocument");
			xmlDoc.async = true;
			xmlDoc.ondataavailable = function ()
			{
				set_status( "<strong>Status:</strong> Receiving KML Data...", (parseInt(xmlDoc.text.length/filesize*100)));
			}
			xmlDoc.onreadystatechange = function ()
			{
				if( xmlDoc.readyState == 2 )
				{
					set_status("<strong>Status:</strong> Waiting for KML Data...");
				}
				else if( xmlDoc.readyState == 4 ) 
				{
					//Something went wrong - most likely file not found.  
					if( xmlDoc.text.length == 0 )
					{
						alert("jKMLMap Error: The KML file you requested could not be downloaded.");
						set_status("<strong>Status:</strong> KML Download Error.");
						return;
					}
					load_finish( xmlDoc );
				}
			}
			xmlDoc.load(kmlfile);
		}
		else if (document.implementation && document.implementation.createDocument)
		{	// code for Mozilla, Firefox, Opera, etc.
			var xmlDoc = new XMLHttpRequest();
			xmlDoc.onreadystatechange = function ()
			{
				if( xmlDoc.status != 200 && xmlDoc.status != 0 )
				{
					alert("jKMLMap Error: The KML file you requested could not be downloaded (Status: " + xmlDoc.status + ").");
					set_status("<strong>Status:</strong> KML Download Error.");
					xmlDoc.abort();
					return;
				}
				if( xmlDoc.readyState == 2 )
				{
					set_status("<strong>Status:</strong> Waiting for KML Data...");
				}
				else if( xmlDoc.readyState == 3 )
				{
					var percentDone = 0;
					if( xmlDoc.responseText )			percentDone = parseInt((xmlDoc.responseText.length+0.00000001) / xmlDoc.getResponseHeader("Content-Length")*100);
					else if( xmlDoc.responseStream ) 	percentDone = parseInt((xmlDoc.responseStream.length+0.00000001) / xmlDoc.getResponseHeader("Content-Length")*100);
					set_status("<strong>Status:</strong> Receiving KML Data...", percentDone );
				}
				else if( xmlDoc.readyState == 4 )
				{
					load_finish( (new DOMParser()).parseFromString( xmlDoc.responseText,"text/xml" ) );
				}
			}
			xmlDoc.open("GET", kmlfile, true);
	        xmlDoc.send(null);
		}
		else
			set_status("<strong>Status:</strong> This script is not supported by your browser!");
	};
	

	//////////////////////////////////////////////////////////////////////
	//Will be automatically invoked by request_data when the KML has finished downloading.
	//Loading from here is broken into several subtasks invoked via setTimeout,
	//to give the browser a chance to update its display (i.e. so elements can be updated in the progress callback, if desired)
	var load_finish = function( root )
	{
		xmlDocRoot = root;
		set_status("<strong>Status:</strong> Parsing KML Data...");
		setTimeout( load1_traversekml, 1 );
	};
	
	
	////////////////////////////////////////////////////////////////////
	//Build the marker array by starting a recursive traversal on the KML document's root node
	var load1_traversekml = function()
	{
		var folderSizeStack = new Array();
		traverse( xmlDocRoot.getElementsByTagName("Document")[0], folderSizeStack );
		delete folderSizeStack;
		set_status("<strong>Status:</strong> Adding Markers...");
		setTimeout( load2_addoverlays, 1 );
	};
	
	
	////////////////////////////////////////////////////////////////////
	//Add the marker overlays to the map and prepare to start building the menu
	var load2_addoverlays = function()
	{
		//Add markers and polylines.
		//NOTE that as of API v3, we could do this as the overlays are constructed by specifying a "map", but I retain
		//the old implementation just for simplicity (for now) during the upgrade...
		for( var i = 0; i < markers.length; i++ )
		{
			if( markers[i].type == MTypes.POINT || markers[i].type == MTypes.POLYLINE )
				markers[i].setMap(_self.map);
		}
		
		//If a menudiv wasn't specified, we're done - go straight to the end.
		if( menudiv == null )
		{
			set_status( "<strong>Status:</strong> Finishing Up...");
			setTimeout( load4_finish, 1 );
			return;
		}
	
		//Otherwise we need to start building the menu.
		//To do this I use 2 stacks: one to keep track of the elements (folders), where the top is our menu's top "form" and a new one is pushed each time I encounter a collapsable folder.
		//The other countStack is a stack of the number of items in each level of folders: when we've completed processing all the items in a folder, it's done and we pop it off.
		//When finished, the elementstack should again contain just one element: the top menuform, which now contains all the nodes in our menu.
		var menuForm = document.createElement("form");
		menuForm.setAttribute("name", "menuform");
		elmntStack = new Array(menuForm);
		countStack = new Array();
		menuIndex = 0;
		lastPercentage = -1;
		set_status("<strong>Status:</strong> Building Menu...");		
		setTimeout( load3_buildmenu, 1 );
	}
	
	
	//////////////////////////////////////////////////////////////////////
	//Build the menu.  This is broken up into sequential calls of this function, where each call processes one item
	//and increases the menuIndex before calling itself for the next.  TODO: This should be re-implemented as a loop;
	//the current (roundabout) method is left over from an older and completely different implementation of jKMLMap.
	var load3_buildmenu = function()
	{
		var shown         = (countStack.length!=1);	//Start with only the top-level folder expanded
		var thisMarker    = markers[menuIndex];
		var thisContainer = elmntStack[elmntStack.length-1];
		var newNode       = null;
		
		//FOLDER: Output its line and start a new div for its contents.		
		if( thisMarker.type == MTypes.FOLDER )
		{
			//<a href="javascript:void(0)" class="kmlFolderBtn" onclick="TogDiv('Fldr00')" ></a>
			var isEmpty = thisMarker.size == 0;
			newNode = document.createElement("a");
			newNode.setAttribute("href", "javascript:void(0)");
			if( isEmpty )	newNode.setAttribute('class', "kmlFolderEmpty");
			else			newNode.setAttribute('class', "kmlFolderBtn");
			if(document.all)
			{
				if( isEmpty )   newNode.setAttribute('className', "kmlFolderEmpty"); 
				else			newNode.setAttribute('className', "kmlFolderBtn"); //Stupid IE7: http://www.quirksmode.org/bugreports/archives/2005/03/setAttribute_does_not_work_in_IE_when_used_with_th.html
			}
			if( !isEmpty ) newNode.onclick = (function(obj,ind){return function(){ obj.TogDiv('Fldr'+ind); }})(_self,menuIndex);
			thisContainer.appendChild(newNode);
			
			//<input type="checkbox" name="list" value="00" onclick="TogMrk(00, document.menuform.list, this.checked, true)" checked="true"/>
			newNode = document.createElement("input");
			newNode.setAttribute("type", "checkbox");
			newNode.setAttribute("name", "list");
			newNode.setAttribute("id", "list"); //Needed for stupid IE7
			newNode.setAttribute("value", menuIndex);
			newNode.setAttribute('checked', "true");
			if(document.all) newNode.setAttribute('defaultChecked', "true"); //Stupid IE7: http://webbugtrack.blogspot.com/2007/11/bug-299-setattribute-checked-does-not.html
			newNode.onclick = (function(obj,ind){return function(){ obj.TogMrk(ind, document.getElementsByName('list'), this.checked, true); }})(_self,menuIndex);
			thisContainer.appendChild(newNode);
			
			//<a title="thisMarker.description"><strong>thisMarker.name</strong></a>
			newNode = document.createElement("a");
			newNode.setAttribute("title", thisMarker.description);
			newNode.innerHTML = "<strong>"+thisMarker.name+"</strong>";
			thisContainer.appendChild(newNode);
			
			//<br />
			thisContainer.appendChild(document.createElement("br"));
			
			//<div id="Fldr00" style="display:none;" class="fldr"> (contains sub items)
			newNode = document.createElement("div");
			newNode.setAttribute("id", "Fldr" + menuIndex);
			newNode.setAttribute('class', "fldr");
			if( document.all )  newNode.setAttribute('className', "fldr"); //stupid IE7
			if( document.all )	newNode.style.setAttribute('cssText', (shown?"":"display:none;")); //For stupid IE
			else				newNode.setAttribute("style", (shown?"":"display:none;"))
			thisContainer.appendChild(newNode);
			elmntStack.push(newNode);
	
			//Remember how many items each folder has.  When we've output every item in the current folder, we pop it off and continue with the parent.
			countStack.push( thisMarker.size+1 );
		}
	
		//POINT and POLYLINE markers
		if( thisMarker.type == MTypes.POINT || thisMarker.type == MTypes.POLYLINE )
		{
			//<input class="mrkr" type="checkbox" name="list" value="00" onclick="TogMrk(00, document.menuform.list, this.checked, true)" checked="true" />
			newNode = document.createElement("input");
			newNode.setAttribute('class', "mrkr");
			if(document.all) newNode.setAttribute('className', "mrkr"); //Stupid IE7
			newNode.setAttribute("type", "checkbox");
			newNode.setAttribute("name", "list");
			newNode.setAttribute("id", "list"); //Stupid IE7 (http://www.ozzu.com/programming-forum/dom-getelementsbyname-and-t57770.html)
			newNode.setAttribute("value", menuIndex);
			newNode.setAttribute('checked', "true");
			if(document.all) newNode.setAttribute('defaultChecked', 'true'); //Stupid IE7
			newNode.onclick = (function(obj,ind){return function(){ obj.TogMrk(ind, document.getElementsByName('list'), this.checked, true); }})(_self,menuIndex);
			thisContainer.appendChild(newNode);
			
			//<a class="haslnk" href="javascript:markers[menuIndex].onClick()" title="descr">thisMarker.name</a>
			newNode = document.createElement("a");
			if(thisMarker.type == MTypes.POINT)
			{
				newNode.setAttribute('class', (thisMarker.hasLnk==true?"haslnk":"nolnk"));
				if(document.all)newNode.setAttribute('className', (thisMarker.getIcon()==mkrLinks?"haslnk":"nolnk"));
				newNode.setAttribute("href", "javascript:void(0);");
				newNode.onclick = (function(mkrs,ind){return function(){ mkrs[ind].onClick(); }})(markers,menuIndex); 
			}
			var descr = thisMarker.description.replace(/<a href=[^>]+>(.+?)<\/a>/i, "$1").replace(/<br \/>.*/, "...");
			newNode.setAttribute("title", descr);
			
			//For me only: parse off the leading 2010/01/01 - dates before the location name...
			if( isJustin )  newNode.innerHTML = thisMarker.name.replace(/....\/..\/.. - /, '');
			else			newNode.innerHTML = thisMarker.name;
			thisContainer.appendChild(newNode);
			
			//<br />
			thisContainer.appendChild(document.createElement("br"));
		}
	
		//Since we've just output one more item, decrement all of the folders' item counts.  
		//If we've output menu items for everything in the top folder, pop it off the stack.
		if( countStack.length > 0 )
		{
			for( var j = 0; j < countStack.length; j++ )	countStack[j]--;
			while( countStack[countStack.length-1] == 0 )
			{
				countStack.pop();
				elmntStack.pop();
			}
		}
		
		//If there are more markers to process, update the progress and call this function again.
		//Otherwise, we're done - move on to the next step.
		menuIndex++;
		if( menuIndex < markers.length )
		{
			load3_buildmenu();
		}
		else
		{
			set_status( "<strong>Status:</strong> Finishing Up...");
			setTimeout( load4_finish, 1 );
		}
	};
	
	
	//////////////////////////////////////////////////////////////////////
	//Wrap up the building of the menubar and we're done!
	var load4_finish = function()
	{
		if( menudiv!= null && elmntStack.length != 1 ) alert("jKMLMap Error - I screwed up the elmntStack! Please report this as a bug.");
		
		//Clear any previous children of menudiv by shallow-cloning and replacing it (fastest way to delete children)
		//See: http://www.highdots.com/forums/javascript/removing-all-children-element-274923.html
		if( menudiv != null )
		{
			var tmp = menudiv.cloneNode(false);
			menudiv.parentNode.replaceChild(tmp, menudiv);
			menudiv = tmp;
			menudiv.appendChild(elmntStack[0]);
			countStack = null;
			elmntStack = null;
		}
		
		//If no view has been set (because there was no top-level folder, or it didn't have a LookAt tag), use the first available marker
		if( !viewWasSet )
		{
			for(var i = 0; i < markers.length; i++)
			{
				if( markers[i].type == MTypes.POINT )
				{
					_self.map.setCenter(markers[i].getLatLng());
					break;
				}
				if( markers[i].type == MTypes.POLYLINE )
				{
					var path = markers[i].getPath();
					var latLng = path.getAt(path.getLength()-1);
					_self.map.setCenter(latLng);
					break;
				}
			}
		}
		
		//Final user callback
		if( callback_finished != null ) callback_finished();
	};
	
	
	/////////////////////////////////////////////////////////////////////////
	//Traverse the subtree of "root," filling up the "markers" array as we go.
	var traverse = function( thisRoot, folderSizeStack )
	{
		if(typeof(thisRoot) == 'undefined')
		{
			alert("jKMLMap Error: The KML file could not be parsed.  Please be sure it's a correctly formatted KML file, and that it's not empty (has at least one marker or path).");
			return;
		}
		for ( var i = 0; i < thisRoot.childNodes.length; i++)
		{
			//Skip non-element nodes
			if( thisRoot.childNodes[i].nodeType != 1 )
				continue;
			
			//Parse Placemark nodes
			else if( thisRoot.childNodes[i].nodeName == "Placemark" )
			{
				var newMarker = parse_object( thisRoot.childNodes[i] )
				if( newMarker ) markers.push( newMarker );
			}
	
			//Parse Folder nodes, then recursively traverse their subnodes.
			if( thisRoot.childNodes[i].nodeName == "Folder" )
			{
				//If this is the first (top-level) folder, use its LookAt node to set the view of the map!
				if( markers.length == 0 ) set_view_from_node(thisRoot.childNodes[i])
				
				folderSizeStack.push( markers.length );
				var newMarker = parse_object( thisRoot.childNodes[i] );
				if( newMarker ) markers.push( newMarker );
				traverse( thisRoot.childNodes[i], folderSizeStack );
	            var folderStartIndex = folderSizeStack.pop();
				markers[folderStartIndex].size = markers.length-folderStartIndex-1;
			}
			
			//Some KML's contain additional DOCUMENT nodes nested within folders; traverse into them.
			if( thisRoot.childNodes[i].nodeName == "Document" )
				traverse( thisRoot.childNodes[i], folderSizeStack );
		}
	};
	

	////////////////////////////////////////////////////////////////////
	//Parse a Folder or Placemark node, returning the appropriate object.
	var parse_object = function( thisObject )
	{
		var retVal		= 0;
		var name		= 0;
		var description	= 0;
		var type		= 0;
		var coords		= 0;
		
		//Determine the appropriate type for this node (and if it's a marker, make sure it has coordinates)
		//console.log(thisObject.nodeName + " " + markers.length + " (" + thisObject.childNodes[1].childNodes[0].nodeValue + ")");
		if( thisObject.nodeName == "Folder" )
			type = MTypes.FOLDER;
		else if( thisObject.nodeName == "Placemark" && thisObject.getElementsByTagName("Point").length > 0 )
			type = MTypes.POINT;
		else if( thisObject.nodeName == "Placemark" && thisObject.getElementsByTagName("LineString").length > 0 )
			type = MTypes.POLYLINE;
		else
			return null;
	
		//Parse its name and description
		for( var i = 0; i < thisObject.childNodes.length; i++ )
		{
			switch( thisObject.childNodes[i].nodeName )
			{
				case "name":		name 		= thisObject.childNodes[i].childNodes[0].nodeValue;
				break;
				case "description":
					if( isJustin )  description = thisObject.childNodes[i].childNodes[0].nodeValue.replace( /<br \/>Marker: <marker>(.+?)<\/marker>/gi, "").replace( /Marker: <marker>(.+?)<\/marker>/gi, "");
					else			description = thisObject.childNodes[i].childNodes[0].nodeValue;
				break;
			}
		}	
	
		//Parse its coordinates
		if( type == MTypes.POINT )
		{
			coords			= thisObject.getElementsByTagName("Point")[0].getElementsByTagName("coordinates")[0].childNodes[0].nodeValue.split(",");
			coords			= new google.maps.LatLng(parseFloat(coords[1]), parseFloat(coords[0]));
		}
		else if( type == MTypes.POLYLINE )
		{
			coords			= new Array();
			var coordsTmp 	= thisObject.getElementsByTagName("LineString")[0].getElementsByTagName("coordinates")[0].childNodes[0].nodeValue.split(/\s+/);
			for (var i = 0; i < coordsTmp.length-1; i++)
			{
				var point = coordsTmp[i].split(",");
				if(point.length != 3) continue;
				coords.push( new google.maps.LatLng(parseFloat(point[1]), parseFloat(point[0])) );
			}
		}
	
		//Create the actual object and copy in the data
		if( type == MTypes.FOLDER )
		{
			retVal				= new Object();
			retVal.name			= name;
			retVal.type 		= type;
			retVal.description 	= description?description:"No Description";
		}
		else if( type == MTypes.POINT )
		{
			var marker_icon;
			var hasLnk = false;
			if( description && description.search(mkrSpecialRegex) != -1)		marker_icon = mkrSpecial;
			else if( description && description.search(/http:\/\//) != -1 )
			{
				marker_icon = mkrLinks;
				hasLnk = true;
			}
			else																marker_icon = mkrBasic;
			retVal				= new google.maps.Marker({position:coords, title: name, icon: marker_icon } );
			if( marker_icon == mkrSpecial ) retVal.zIndex = 999;
			retVal.name			= name;
			retVal.type 		= type;
			retVal.hasLnk 		= hasLnk;
			retVal.description 	= description?description:"No Description";
			retVal.onClick		= function() 
			{ 
				var infoWindow = new google.maps.InfoWindow({content:"<h3>" + this.name + "</h3>" + this.description});
				if( _self.openedInfoWindow != null ) _self.openedInfoWindow.close();
				infoWindow.open(this.map, this);
				_self.openedInfoWindow = infoWindow;
			}
			google.maps.event.addListener(retVal, 'click', retVal.onClick);
		}
		else if( type == MTypes.POLYLINE )
		{
			retVal				= new google.maps.Polyline({path:coords, strokeColor:"#FF0000", strokeWeight:3, strokeOpacity:0.9});
			retVal.name			= name;
			retVal.type 		= type;
			retVal.description 	= description?description:"No Description";
		}
	
		//Done!
		return retVal;
	};
	
	
	//////////////////////////////////////////////////////////////////////
	//Given a Folder or Placemark node, use its LookAt subnode to center the map.
	//If no LookAt child is found, has no effect. 
	var set_view_from_node = function( theNode )
	{
		for ( var j = 0; j < theNode.childNodes.length; j++)
		{
			if( theNode.childNodes[j].nodeName == "LookAt" )
			{
				var lat = theNode.childNodes[j].getElementsByTagName('latitude')[0].childNodes[0].nodeValue;
				var lng = theNode.childNodes[j].getElementsByTagName('longitude')[0].childNodes[0].nodeValue;
				var range = theNode.childNodes[j].getElementsByTagName('range')[0].childNodes[0].nodeValue;
				var zoom = Math.round(26 - (Math.log(range)/Math.log(2))); //From: http://groups.google.com/group/google-earth-browser-plugin/browse_thread/thread/2832a0541173cd39/9f513958ebd7959b?lnk=gst&q=
				if(zoom < 0)  zoom = 0;
				if(zoom > 21) zoom = 21; 
				_self.map.setCenter( new google.maps.LatLng(lat, lng) );
				_self.map.setZoom( zoom );
				viewWasSet = true;
				//console.log("Set View: " + lat + ", " + lng + ", " + zoom);
			}
		}
	};
	
	
	///////////////////////////////////////////////////////////////////////
	//Call the onProgress callback with a message and percentage, so the user can show load status
	var set_status = function( msg, percentage )
	{
		if( callback_progress != null )	
			callback_progress( msg, typeof(percentage)=="undefined"?0:percentage );
	};
	
	
	///////////////////////////////////////////////////////////////////////
	//Calls to this function are generated in the menu; it handles clicks to the checkboxes.  
	//->If it was a marker checkbox, that marker is added/removed from the GMap.
	//->If it was a folder checkbox, all contained checkboxes are toggled.
	this.TogMrk = function( index, checkboxes, topLevelCheck, topLevel )
	{
		//Since the fxn can be called recursively, we don't want nested subdirs to call all their
		//contained markers when the top-level folder has already done so.  topLevelCheck thus
		//remembers the state of the "master" checkbox, and topLevel tells if whether or not this 
		//is the top-level folder.  If so, recurse; otherwise, contained markers will be handled 
		//by the higher-level folder looping through its contents.
		checkboxes[index].checked	= topLevelCheck;
		
		//If it was a folder, hide/show the menu item and recursively add/remove all of its subitems
		if( markers[index].type == MTypes.FOLDER )
		{
			if( topLevel )
			{
				for( var i = 0; i < markers[index].size; i++ )
					this.TogMrk( i+index+1, checkboxes, topLevelCheck, false );
			}
		}
		//If it a point or polyline, just add/remove it normally.
		else
		{
			if( topLevelCheck )	markers[index].setMap(this.map);
			else				markers[index].setMap(null);
		}	
	};
	
	
	///////////////////////////////////////////////////////////////////////
	//Calls to this function are generated in the menu; it handles expanding/collapsing the folders
	this.TogDiv = function( whichDiv )
	{
		var style_value = 0;
		if( document.getElementById )	style_value = document.getElementById( whichDiv ).style;	//Standard web browsers
		else if( document.all )			style_value = document.all[whichDiv].style;					//Old versions of IE
		else if( document.layers )		style_value = document.layers[whichDiv].style;				//nn4 browsers
		else							return;
		style_value.display = (style_value.display=="none"?"block":"none");
	};
}


///////////////////////////////////////////////////////////////////////
//A global convenience function to create and return a new icon using the specified image file.
//"centered" determines if the marker-point is at its bottom (like a pushpin) or center(like a target)
///////////////////////////////////////////////////////////////////////
function CreateGIcon( fileName, width, height, centered )
{
	var anchorVert			= height;
	if( centered ) anchorVert = height/2;
	var retVal 				= new google.maps.MarkerImage();
	retVal.url				= fileName;
	retVal.scaledSize		= new google.maps.Size(width, height);
	retVal.anchor	 		= new google.maps.Point(width/2, anchorVert);
	return retVal;
}
