﻿//globals
var ifpMap;
var currentMap;
var lastPrefix;

function logIt(message)
{
	var field = document.getElementById("MapsLog");
	if (field) field.value = field.value + ", " + message;
}

//enumerations

//AirSigmet class
function ifpAirSigmet(airSigmetType,hazardType,severity,valid,minAlt,maxAlt,movementDir,movementSpeed)
{
	this.airSigmetType = airSigmetType;
	this.hazardType = hazardType;
	this.severity = severity;
	this.valid = valid;
	this.minAlt = minAlt;
	this.maxAlt = maxAlt;
	this.movementDirection = movementDir;
	this.movementSpeed = movementSpeed;
	this.points = new Array();
	this.infoWindow = null;
}
ifpAirSigmet.prototype.createInfoWindow =
	function()
	{
		if (!this.infoWindow)
		{
			var content = '<div class="GmInfo GmAirmet">';
			content += '<div class="Title">' + this.airSigmetType + ": " + this.hazardType + '</div>'
			content += '<div class="Label">Severity</div><div class="Item">' + this.severity + '&nbsp;</div>'
			if (this.minAlt != 0 || this.maxAlt != 0)
				content += '<div class="Label">Altitude</div><div class="Item">' + this.minAlt + ' - ' + this.maxAlt + ' MSL</div>'
			content += '<div class="Label">Valid</div><div class="Item">' + this.valid + '</div>'
			if (this.movementDirection != 0 || this.movementSpeed != 0)
				content += '<div class="Label">Moving</div><div class="Item">' + this.movementDirection + '&deg; at ' + this.movementSpeed + 'KTS</div>'
			content += '</div>'
			
			this.infoWindow = new google.maps.InfoWindow
						({
							content: content
						});	
		}
	}
	
//Tfr class
function ifpTfr(id,name,fullName,minAlt,maxAlt,valid,purpose)
{
	this.id = id;
	this.name = name;
	this.fullName = fullName;
	this.valid = valid;
	this.minAlt = minAlt;
	this.maxAlt = maxAlt;
	this.purpose = purpose;
	this.points = new Array();
	this.infoWindow = null;
}
ifpTfr.prototype.createInfoWindow =
	function()
	{
		if (!this.infoWindow)
		{
			var content = '<div class="GmInfo GmTfr">';
			content += '<div class="Title">' + this.fullName + '</div>'
			if (this.minAlt != 0 || this.maxAlt != 0)
				content += '<div class="Label">Altitude</div><div class="Item">' + this.minAlt + ' - ' + this.maxAlt + ' MSL</div>'
			content += '<div class="Label">Valid</div><div class="Item">' + this.valid + '</div>'
			if (this.purpose && this.purpose.length)
				content += '<div class="Label">Purpose</div><div class="Item">' + this.purpose + '</div>'
			content += '<div class="Label">Links</div><div class="Item"><a href="http://tfr.faa.gov/save_pages/detail_' + this.id.replace("/","_") + '.html" target="_blank">TFR.FAA.GOV Detail</a></div>'	
				
			content += '</div>'
			
			this.infoWindow = new google.maps.InfoWindow
						({
							content: content
						});	
		}
	}
	
//Wunderground Radar & Satellite Overlay
function RadarSatOverlay(map,visible,isSatellite,prefix) {
	this.map = map;
	this.div = null;
	this.img = null;
	this.hiddenChange = false;
	this.visible = visible;
	this.idleListener = null;
	this.imageUrl = null;
	this.isSatellite = isSatellite;
	this.newLeft = null;
	this.newTop = null;
	this.newHeight = null;
	this.newWidth = null;
	this.prefix = prefix;
	this.setMap(map);
}
RadarSatOverlay.prototype = new google.maps.OverlayView();
RadarSatOverlay.prototype.onRemove = function() {this.div.parentNode.removeChild(this.div); maps.google.event.removeListener(this.idleListener);}
RadarSatOverlay.prototype.beginLoading = function() {if (this.div) {this.div.style.visibility = "hidden";}}
RadarSatOverlay.prototype.endLoading = function() {if (this.div && this.visible) {this.div.style.left = this.newLeft; this.div.style.top = this.newTop; this.div.style.width = this.newWidth; this.div.style.height = this.newHeight; this.div.style.visibility = "visible";}}
RadarSatOverlay.prototype.hide = function() {if (this.div) {this.div.style.visibility = "hidden"; this.visible = false;}}
RadarSatOverlay.prototype.show = function() {if (this.div) {this.div.style.visibility = "visible"; this.visible = true; if (this.hiddenChange) {this.draw();}}}
RadarSatOverlay.prototype.onAdd =
	function()
	{
		logIt("onAdd Start");
		var me = this;
	
		//create container div
		var div = document.createElement("div");
		div.style.border = "none";
		div.style.borderWidth = "0px";
		div.style.height = "100%";
		div.style.left = "0px";
		div.style.position = "absolute";
		div.style.top = "0px";
		div.style.width = "100%";
		
		//create img
		var img = document.createElement("img");
		img.style.width = "100%";
		img.style.height = "100%";
		//img.style.border = "solid 5px red";
		div.appendChild(img);
		
		//set properties
		this.div = div;
		this.img = img;

		//add to overlayLayer pane (just above map)
		var panes = this.getPanes();
		panes.overlayLayer.appendChild(this.div);
		
		//add loading function so we can reshow after radar is loaded
		img.onload = function() {me.endLoading();}
		
		//add listeners
		if (!this.idleListener)
			this.idleListener = google.maps.event.addListener(ifpMap.map, "idle", function() {logIt("idle");me.draw(true);})
		
		logIt("onAdd End");
	}

RadarSatOverlay.prototype.draw = function(isDragOrPan)
	{
		logIt("draw Start");
	
		//clear existing timeout (to cancel requests to radar api)
		window.clearTimeout(this.timeout);
		if (this.visible)
		{
			var imgUrl;
			
			//if not drag or pan, call begin loading to hide previous image
			if (!isDragOrPan)
				this.beginLoading();
		
			var projection = this.getProjection();
			var bounds = this.map.getBounds();
			var div = this.map.getDiv();
			var ne = bounds.getNorthEast();
			var sw = bounds.getSouthWest();
			var me = this;
			var left, top;		
			var el, satType;
			
			if (!this.isSatellite)
				imgUrl = "http://radblast.wunderground.com/cgi-bin/radar/WUNIDS_composite?type=N0R&frame=0&num=6&delay=20&png=0&smooth=1&min=2&noclutter=1&rainsnow=1&nodebug=0&theext=.gif&timelabel=1&timelabel.x=8&timelabel.y=14&reproj.automerc=1&merge=elev";
			else
			{
				//determine type
				el = document.getElementById(this.prefix + "_SatelliteI");
				satType = (el && el.checked ? "sat_ir4" : "sat_vis");
				imgUrl = "http://wublast.wunderground.com/cgi-bin/WUBLAST?gtt=109&frame=0&num=6&delay=20&proj=me&timelabel=1&timelabel.x=110&timelabel.y=14&key=" + satType;
				this.setOpacity(70);
			}
			
			//add properties
			imgUrl += "&maxlat=" + ne.lat();
			imgUrl += "&minlat=" + sw.lat();
			imgUrl += "&maxlon=" + ne.lng();
			imgUrl += "&minlon=" + sw.lng();
			imgUrl += "&width=" + div.offsetWidth;
			imgUrl += "&height=" + div.offsetHeight;
			imgUrl += "&rand=" + Math.random();
			logIt("width: " + div.offsetWidth + "\n");
			logIt("height: " + div.offsetHeight + "\n");
			logIt("URL: " + imgUrl + "\n");
			
			var sw2 = projection.fromLatLngToDivPixel(sw);
			var ne2 = projection.fromLatLngToDivPixel(ne);

			// Resize the image's DIV to fit the indicated dimensions.
			this.newLeft = sw2.x + "px";
			this.newTop = ne2.y + "px";
			this.newWidth = (ne2.x - sw2.x) + "px";
			this.newHeight = (sw2.y - ne2.y) + "px";
			
			//set img url, then timeout (this helps prevent multiple calls in a row to wunderground)
			this.imageUrl = imgUrl;
			if (this.map.getZoom() > 3)
				this.timeout = window.setTimeout(function() {me.setImgSrc();}, 1500);
			this.hiddenChange = false;
		}
		else
			this.hiddenChange = true;
			
		logIt("draw End");
	}
RadarSatOverlay.prototype.setImgSrc = function() {this.img.src = this.imageUrl;}
RadarSatOverlay.prototype.setOpacity = 
	function(op)
	{
		if (op < 0) opacity = 0;
		if (op > 100) opacity = 100;
		var c = op/100;
		if (typeof(this.div.style.filter) =='string')
			this.div.style.filter = 'alpha(opacity:' + op + ')';
		if (typeof(this.div.style.KHTMLOpacity) == 'string')
			this.div.style.KHTMLOpacity = c;
		if (typeof(this.div.style.MozOpacity) == 'string')
			this.div.style.MozOpacity = c;
		if (typeof(this.div.style.opacity) == 'string')
			this.div.style.opacity = c;
	}
RadarSatOverlay.prototype.toggle =
	function()
	{
		logIt("toggle Start");
		if (this.div)
		{
			if (this.visible)
				this.show();
			else 
				this.hide();
		}
		logIt("toggle End");
	}

/**********************/
/**********************/
/* iFlightPlannerMap */
function iFlightPlannerMap(id,routeFieldID,fromFieldID,toFieldID,altFieldID,markersBaseUrl,tilesBaseUrl,sizingHandler,routeCustomizeHandler,routeUpdateHandler,routeUpdatePrefix,afdUrl,radarSatelliteEnabled,routesEnabled,drawRouteOnLoad,routeInitFn,standalone,navLogViewID,imgRoot)
	{
		ifpMap = this;
		this.id = id;
		this.markersBaseUrl = markersBaseUrl;
		this.tilesBaseUrl = tilesBaseUrl;
		this.radarSatelliteEnabled = radarSatelliteEnabled;
		this.routesEnabled = routesEnabled;
		this.routeFieldID = routeFieldID;
		this.fromFieldID = fromFieldID;
		this.toFieldID = toFieldID;
		this.altFieldID = altFieldID;
		this.sizingHandler = sizingHandler;
		this.routeCustomizeHandler = routeCustomizeHandler;
		this.routeUpdateHandler = routeUpdateHandler;
		this.routeUpdatePrefix = routeUpdatePrefix;
		this.afdUrl = afdUrl;
		this.standalone = standalone;
		this.navLogViewID = navLogViewID;
		this.imgRoot = imgRoot;
		
		//reusable marker values
		this.imageOrigin = new google.maps.Point(0,0);
		this.aptImageAnchor = new google.maps.Point(10,28);
		this.aptImageSize = new google.maps.Size(20,28);
		this.circleImageAnchor = new google.maps.Point(6,6);
		this.circleImageSize = new google.maps.Size(12,12);
		this.shadowImageAnchor = new google.maps.Point(0,21);
		this.shadowImageSize = new google.maps.Size(22,21);
		this.squareImageAnchor = new google.maps.Point(7,7);
		this.squareImageSize = new google.maps.Size(14,14);
		this.xvImageAnchor = new google.maps.Point(9,9);
		this.xvImageSize = new google.maps.Size(18,18);
				
		//determine iPad, iPod, iPhone
		var ua = navigator.userAgent;
		this.isApple = (ua.indexOf("iPad") > -1 || ua.indexOf("iPhone") > -1 || ua.indexOf("iPod") > -1);
				
		//initialize
		this.init(drawRouteOnLoad);
		
		//funky script to prevent race condition in IE
		if (routeInitFn && routeInitFn.length && eval(routeInitFn))
			eval(routeInitFn + "()");
	}
iFlightPlannerMap.prototype.adverseFlightRules = null;
iFlightPlannerMap.prototype.adverseFlightRulesMarkers = new Array();
iFlightPlannerMap.prototype.airSigmets = null;
iFlightPlannerMap.prototype.airSigmetRegions = new Array();
iFlightPlannerMap.prototype.contextMenuMarker = null;
iFlightPlannerMap.prototype.contextUseSnap = false;
iFlightPlannerMap.prototype.controlsContainer = null;
iFlightPlannerMap.prototype.customLocations = null;
iFlightPlannerMap.prototype.customLocationMarkers = new Array();
iFlightPlannerMap.prototype.dates = new Array();
iFlightPlannerMap.prototype.favorites = null;
iFlightPlannerMap.prototype.favoriteMarkers = new Array();
iFlightPlannerMap.prototype.freezingLayer = null;
iFlightPlannerMap.prototype.ifrLowMap = null;
iFlightPlannerMap.prototype.ifrLowAreaMap = null;
iFlightPlannerMap.prototype.ifrLowAreaIndex = 1;
iFlightPlannerMap.prototype.ifrHighMap = null;
iFlightPlannerMap.prototype.imgRoot = "";
iFlightPlannerMap.prototype.infoWindow = null;
iFlightPlannerMap.prototype.initialized = false;
iFlightPlannerMap.prototype.isApple = false;
iFlightPlannerMap.prototype.lastLatLng = null;
iFlightPlannerMap.prototype.map = null;
iFlightPlannerMap.prototype.markers = new Array();
iFlightPlannerMap.prototype.markersBaseUrl = null;
iFlightPlannerMap.prototype.markerType = {ROUTE_PENDING: 0, FLIGHT_RULE: 1, CUSTOM_LOCATION: 2, CORRIDOR_AIRPORT: 3, FAVORITE_AIRPORT: 4, ROUTE_MARKER: 5, ROUTE_AIRPORT: 6}
iFlightPlannerMap.prototype.navLogViewID = null;
iFlightPlannerMap.prototype.radar = null;
iFlightPlannerMap.prototype.routeCorridorMarkers = new Array();
iFlightPlannerMap.prototype.routeCount = 0;
iFlightPlannerMap.prototype.routeMarkers = new Array();
iFlightPlannerMap.prototype.routeMarkersPending = new Array();
iFlightPlannerMap.prototype.routeLines = new Array();
iFlightPlannerMap.prototype.routeLineToSplice = null;
iFlightPlannerMap.prototype.satellite = null;
iFlightPlannerMap.prototype.sectionalMap = null;
iFlightPlannerMap.prototype.sfcLayer = null;
iFlightPlannerMap.prototype.standalone = false;
iFlightPlannerMap.prototype.tacOption = null;
iFlightPlannerMap.prototype.tacIndex = 0;
iFlightPlannerMap.prototype.tacMap = null;
iFlightPlannerMap.prototype.tilesBaseUrl = null;
iFlightPlannerMap.prototype.trafficOption = null;
iFlightPlannerMap.prototype.trafficLayer = null;
iFlightPlannerMap.prototype.tfrs = null;
iFlightPlannerMap.prototype.tfrRegions = new Array();
iFlightPlannerMap.prototype.updateTimer = null;

//init
iFlightPlannerMap.prototype.init =
	function(drawRouteOnLoad,oldmap)
	{
		var me = this;
		var map;
		var latLng = (oldmap ? oldmap.getCenter() : new google.maps.LatLng(37.32, -94.37));
		var zoom = (oldmap ? oldmap.getZoom() : 4);
		var mapTypeID = (oldmap ? oldmap.getMapTypeId() : "sectional");
		var options =
			{
				zoom: zoom,
				center: latLng,
				mapTypeControlOptions:
					{
						mapTypeIds: [google.maps.MapTypeId.ROADMAP,
									google.maps.MapTypeId.SATELLITE,
									google.maps.MapTypeId.TERRAIN,
									google.maps.MapTypeId.HYBRID,
									"sectional","ifrlow","ifrhigh"],
						style: google.maps.MapTypeControlStyle.DEFAULT
					},
				navigationControl: true,
				scaleControl: true,
				scrollwheel: false
			};
		map = new google.maps.Map(document.getElementById("GoogleMap"), options);
		
		//clear old map reference, set new
		if (this.map) this.map = null;
		this.map = map;
			
		//create map types
		if (!this.initialized)
		{
			this.sectionalMap = new google.maps.ImageMapType(this.getSectionalMapOptions());
			this.tacMap = new google.maps.ImageMapType(this.getTACMapOptions());
			this.ifrLowMap = new google.maps.ImageMapType(this.getIFRLowMapOptions());
			this.ifrLowAreaMap = new google.maps.ImageMapType(this.getIFRLowAreaMapOptions());
			this.ifrHighMap = new google.maps.ImageMapType(this.getIFRHighMapOptions());
			this.trafficLayer = new google.maps.TrafficLayer();
		}
		
		//set map types
		map.mapTypes.set("sectional",this.sectionalMap);
		map.overlayMapTypes.insertAt(this.tacIndex,this.tacMap);
		map.mapTypes.set("ifrlow",this.ifrLowMap);
		//map.overlayMapTypes.insertAt(this.ifrLowAreaIndex,this.ifrLowAreaMap);
		map.mapTypes.set("ifrhigh",this.ifrHighMap);
		
		//radar & satellite
		if (this.radarSatelliteEnabled)
		{
			this.satellite = new RadarSatOverlay(map,this.getSatelliteOn(),true,this.id);
			this.radar = new RadarSatOverlay(map,this.getRadarOn(),false,this.id);
		}
					
		//custom controls
		this.createControls();
		
		//listeners
		google.maps.event.addListener(map, "maptypeid_changed", function() {ifpMap.mapTypesChanged();});
		google.maps.event.addListener(map, "zoom_changed", function() {ifpMap.mapTypesChanged();});
		google.maps.event.addListener(map, "center_changed", function() {ifpMap.hideContextMenu();});
		
		//add right click or dblclick handler to show arbitrary context menu
		if (!this.isApple)
			google.maps.event.addListener(map, "rightclick", function(event) {ifpMap.showContextMenu(event,null,false);});
		else //on iPad, etc, disable double click for zoom and use to add points to route
		{
			map.disableDoubleClickZoom = true;
			google.maps.event.addListener(map, "dblclick", function(event) {ifpMap.showContextMenu(event,null,false);});
		}
		google.maps.event.addListener(map, "click", function(event) {ifpMap.hideContextMenu();});
				
		//call sizing handler
		if (this.sizingHandler)
			eval(this.sizingHandler + "(map);");
		
		//set map type
		map.setMapTypeId(mapTypeID);
		
		//add custom locations & favorites and set bounds
		this.toggleCustomLocations();
		this.toggleFavorites();
		if (this.routesEnabled && !oldmap) //HACK: have to use setTimeout to get this to work right
			setTimeout(function(){me.setBoundsFromFavorites()},650); 
		
		//toggle initial options
		this.toggleRadar();
		this.toggleSatellite();
		this.toggleTfrs();
		this.toggleAdverseFlightRules();
		this.getAirSigmets();
		this.toggleSfc(false);
		this.toggleFreezing(false);		
		
		//if requested, show initial route
		if (drawRouteOnLoad)
			this.getRoute();	
			
		//set flag indicating initialized
		this.initialized = true;	
		
		//finally, create update timer to auto-update map conditions
		this.setUpdateTimer();	
	}
	
iFlightPlannerMap.prototype.reinit =
	function()
	{
		this.init(false,this.map);
		
		this.toggleAdverseFlightRules();
		this.toggleCustomLocations();
		this.toggleFavorites();
		this.getAirSigmets();
		this.toggleTfrs();	
		this.toggleTraffic();
		this.toggleSfc(false);
		this.toggleFreezing(false);
		
		this.redrawRoute();		
	}
			

/**********************/
/**********************/
/* Adverse Flight Rules */
iFlightPlannerMap.prototype.clearAdverseFlightRulesMarkers =
	function()
	{
		for (var i=0; i<this.adverseFlightRulesMarkers.length; i++)
		{
			this.adverseFlightRulesMarkers[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.adverseFlightRulesMarkers[i]);
			this.markers[this.adverseFlightRulesMarkers[i].UniqueID] = null;
		}
		this.adverseFlightRulesMarkers.splice(0,this.adverseFlightRulesMarkers.length);
	}

iFlightPlannerMap.prototype.createAdverseFlightRulesMarker = 
	function(location)
	{
		var marker, result;
	
		//ensure marker can be created
		result = this.canCreateMarker(location.UniqueID,this.markerType.FLIGHT_RULE,true);
		if (result.canCreate)
		{
			var title = location.BestID + " - " + location.Name + ": " + location.Type;
			var latLng = new google.maps.LatLng(location.Lat,location.Lng);
			var image = new google.maps.MarkerImage(this.markersBaseUrl + location.FlightRules + "Circle.png",this.circleImageSize,this.imageOrigin,this.circleImageAnchor);
			marker = new google.maps.Marker
						({
							title: title,
							position: latLng,
							icon: image,
							map: this.map,
							zIndex: 85,
							
							//custom properties
							UniqueID: location.UniqueID,
							BestID: location.BestID,
							Class: location.Class,
							Name: location.Name,
							MarkerType: this.markerType.FLIGHT_RULE,
							Distance: 0,
							IsFavorite: result.isFavorite
						});
			
			//listeners
			if (!this.isApple)
			{
				google.maps.event.addListener(marker, "click", function(event) {ifpMap.getLocationInfo(marker);});
				google.maps.event.addListener(marker, "rightclick", function(event) {ifpMap.showContextMenu(event,marker,false);});
			}
			else
				google.maps.event.addListener(marker, "click", function(event) {ifpMap.showContextMenu(event,marker,false);});
			this.adverseFlightRulesMarkers[this.adverseFlightRulesMarkers.length] = marker;
			
			//set marker type, add to overall collection
			marker.UniqueID = location.UniqueID;
			marker.MarkerType = this.markerType.FLIGHT_RULE;
			this.markers[location.UniqueID] = marker;
		}
		else
			marker = result.markerToUse;
		
		return marker;
	}

iFlightPlannerMap.prototype.getAdverseFlightRules =
	function()
	{
		if (this.dataValid("adverseflightrules",5))
			this.showAdverseFlightRules();
		else
			MapsService.GetAdverseFlightRulesLocations(MapsService_GetAdverseFlightRulesLocations_SucceededCallback);
	}	

iFlightPlannerMap.prototype.receiveAdverseFlightRules =
	function(result)
	{
		this.adverseFlightRules = this.parseLocations(result);
		this.dates["adverseflightrules"] = new Date();
		this.showAdverseFlightRules();
	}

iFlightPlannerMap.prototype.reshowAdverseFlightRules =
	function()
	{
		for (var i=0; i<this.adverseFlightRulesMarkers.length; i++)
			this.adverseFlightRulesMarkers[i].setMap(this.map);
	}

iFlightPlannerMap.prototype.showAdverseFlightRules =
	function(result)
	{
		var locations = this.adverseFlightRules;
		
		//clear previous markers
		this.clearAdverseFlightRulesMarkers();
		
		//add markers, create coordinates
		for (var i=0; i<locations.length; i++)
			this.createAdverseFlightRulesMarker(locations[i]);
	}

iFlightPlannerMap.prototype.toggleAdverseFlightRules =
	function()
	{
		var field = document.getElementById(this.id + "_AdverseFltRules");
		if (field)
		{
			if (field.checked)
				this.getAdverseFlightRules();
			else
				this.clearAdverseFlightRulesMarkers();
		}
	}

/**********************/
/**********************/
/* Air/Sigmets */
iFlightPlannerMap.prototype.clearAirSigmetRegions = 
	function()
	{
		for (i=0; i<this.airSigmetRegions.length; i++)
		{
			this.airSigmetRegions[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.airSigmetRegions[i]);
		}
		this.airSigmetRegions.splice(0,this.airSigmetRegions.length);
	}

iFlightPlannerMap.prototype.createAirSigmetRegion =
	function(airSigmet,color,weight)
	{
		//create region as polyline
		var region = new google.maps.Polyline
					({
						path: airSigmet.points,
						strokeColor: color,
						strokeOpacity: .75,
						strokeWeight: weight,
						map: this.map
					});
		
		//add click listener
		google.maps.event.addListener(region, "click", function(event) {airSigmet.createInfoWindow(); ifpMap.showInfoWindow(event,airSigmet.infoWindow);});
		this.airSigmetRegions[this.airSigmetRegions.length] = region;

		return region;
	}

iFlightPlannerMap.prototype.getAirSigmets = 
	function()
	{
		if (this.dataValid("airsigmets",5))
			this.showAirSigmets();
		else
			MapsService.GetAirSigmets(MapsService_GetAirSigmets_SucceededCallback);
	}
	
iFlightPlannerMap.prototype.parseAirSigmets =
	function(data)
	{
		var airSigmets = new Array();
		var airSigmet;
		var values;
		
		if (data && data.length)
		{
			values = data.split("$$");
			for (var i=0; i<values.length; i++)
			{
				var parts = values[i].split("|");
				airSigmet = new ifpAirSigmet(parts[0],parts[1],parts[2],parts[3],parts[4],parts[5],parts[6],parts[7]);
				var points = parts[8].split("ll:");
				for (var j=0; j<points.length; j++)
				{
					var partsLL = points[j].split(",");
					airSigmet.points[airSigmet.points.length] = new google.maps.LatLng(partsLL[0], partsLL[1]);
				}
				airSigmets[airSigmets.length] = airSigmet;
			}
		}
		
		return airSigmets;
	}	

iFlightPlannerMap.prototype.receiveAirSigmets =
	function(result)
	{
		this.airSigmets = this.parseAirSigmets(result);
		this.dates["airsigmets"] = new Date();
		this.showAirSigmets();
	}

iFlightPlannerMap.prototype.showAirSigmets =
	function()
	{
		var airSigmets = this.airSigmets;
		var airSigmet;
		
		var mtnObscColor = "#8F4D00";
		var ifrColor = "#FF0000";
		var turbColor = "#000000";
		var iceColor = "#0000FF";
		var convectiveColor = "#FFFF00";
		var ashColor = "#AFAFAF";
		var unknownColor = "#007F1D";
		var i;
		
		var showIFR = document.getElementById(this.id + "_AirSigmets_IFR").checked;
		var showTurbulence = document.getElementById(this.id + "_AirSigmets_Turbulence").checked;
		var showIcing = document.getElementById(this.id + "_AirSigmets_Icing").checked;
		var showMtn = document.getElementById(this.id + "_AirSigmets_Mtn").checked;
		var showConvective = document.getElementById(this.id + "_AirSigmets_Convective").checked;
		var showAsh = document.getElementById(this.id + "_AirSigmets_Ash").checked;
		var visible;
		
		//clear existing
		this.clearAirSigmetRegions()
		
		//create new regions
		for (i=0; i<airSigmets.length; i++)
		{
			var color, weight;
			airSigmet = airSigmets[i];
			
			if (airSigmet.severity != "None")
			{
				//determine color
				if (airSigmet.hazardType == "Mountain Obscuration")
					{color = mtnObscColor; visible = showMtn;}
				else if (airSigmet.hazardType == "IFR")
					{color = ifrColor; visible = showIFR;}
				else if (airSigmet.hazardType == "Turbulence")
					{color = turbColor; visible = showTurbulence;}
				else if (airSigmet.hazardType == "Icing")
					{color = iceColor; visible = showIcing;}
				else if (airSigmet.hazardType == "Convective")
					{color = convectiveColor; visible = showConvective;}
				else if (airSigmet.hazardType == "Ash")
					{color = ashColor; visible = showAsh;}
				else
					{color = unknownColor; visible = true;}
				
				if (visible)
				{
					//determine width
					if (airSigmet.severity == "Severe")
						weight = 7;
					else if (airSigmet.severity == "Moderate-Severe")
						weight = 6;
					else if (airSigmet.severity == "Moderate")
						weight = 4;
					else if (airSigmet.severity == "Light-Moderate")
						weight = 3;
					else if (airSigmet.severity == "Unknown" || airSigmet.severity == "")
						weight = 2;	
					else
						weight = 1;
					
					//create airSigmet region
					this.createAirSigmetRegion(airSigmet,color,weight);
				}
			}
		}
	}


/**********************/
/**********************/
/* Custom Controls */
iFlightPlannerMap.prototype.createControls =
	function()
	{
		var div;
		var container = document.createElement("div");
		container.className = "GmControls";
		this.controlsContainer = container;
				
		//header
		var header = document.createElement("h3");
		header.innerHTML = "Map Options";
		container.appendChild(header);		
		
		var ctls = document.createElement("div");
		ctls.className = "Controls";
		container.appendChild(ctls);
				
		//TAC checkbox
		this.tacOption = this.createCheckboxDiv("TAC","Show TACs",true,"ifpMap.toggleTAC();","",ctls)
		
		//Traffic TAC checkbox
		this.trafficOption = this.createCheckboxDiv("Traffic","Traffic",false,"ifpMap.toggleTraffic();","none",ctls)
		
		//event handlers
		this.map.controls[google.maps.ControlPosition.RIGHT].push(container);
	}
	
iFlightPlannerMap.prototype.createCheckboxDiv =
	function(id,text,checked,fn,display,parent)
	{
		//create checkbox
		var div = document.createElement("div");
		div.className = "Control";
		div.style.display = display;
		var chk = document.createElement("input");
		chk.type = "checkbox";
		chk.checked = checked;
		chk.id = this.id + "_" + id;
		chk.setAttribute("onclick", fn)
		var lbl = document.createElement("label");
		lbl.setAttribute("for",chk.id);
		lbl.innerHTML = text;
		div.appendChild(chk);
		div.appendChild(lbl);
		parent.appendChild(div);
		
		return div;
	}

/**********************/
/**********************/
/* Custom Location Methods */
iFlightPlannerMap.prototype.clearCustomLocation =
	function()
	{
		document.getElementById(this.id + "_CustomLocationFields_CustomLocationName").value = "";
		document.getElementById(this.id + "_CustomLocationFields_CustomLocationDesc").value = "";
		modalDialog_toggle(this.id + "_CustomLocationDialog",false);
	}
	
iFlightPlannerMap.prototype.clearCustomLocations = 
	function()
	{
		//clear previous custom location markers
		for (var i=0; i<this.customLocationMarkers.length; i++)
		{
			this.customLocationMarkers[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.customLocationMarkers[i]);
			this.markers[this.customLocationMarkers[i].UniqueID] = null;
		}
		this.customLocationMarkers.splice(0,this.customLocationMarkers.length);
	}

iFlightPlannerMap.prototype.getCustomLocations = 
	function()
	{
		MapsService.GetCustomLocations(MapsService_GetCustomLocations_SucceededCallback);
	}

iFlightPlannerMap.prototype.receiveCustomLocations =
	function(result)
	{
		var locations = jsonParse(result);
		var marker;
		
		//create marker for each custom location
		for (var i=0; i<locations.length; i++)
		{
			marker = this.createMarker(locations[i],false,this.markerType.CUSTOM_LOCATION,90,true);
			if (marker) this.customLocationMarkers[this.customLocationMarkers.length] = marker;
		}
	}

iFlightPlannerMap.prototype.receiveSaveCustomLocation =
	function(result)
	{
		var data;
		var location, bestID, name, latLng, marker;
		
		//ensure valid result
		data = jsonParse(result);
		if (data.status == 0)
		{
			//create location, then create Custom Location marker
			bestID = document.getElementById(this.id + "_CustomLocationFields_CustomLocationName").value;
			name = document.getElementById(this.id + "_CustomLocationFields_CustomLocationDesc").value;
			latLng = this.lastLatLng;
			
			//create new Custom Location marker
			location = this.createLocationObject(data.uniqueID,bestID,bestID,name,"C","","",latLng.lat(),latLng.lng());
			marker = this.createMarker(location,false,this.markerType.CUSTOM_LOCATION,90,false);
			if (marker) this.customLocationMarkers[this.customLocationMarkers.length] = marker;
			
			//if use snap, snap context marker to custom location
			if (this.contextUseSnap)
				this.snapRouteMarkerTo(data.uniqueID,bestID,name,"C","","",latLng.lat(),latLng.lng());
			
			//clear custom location info and provide message
			this.clearCustomLocation();
		}
		else //if (data.status == 1)
			alert("The Location Name you entered is already in use in your custom location list.\n\nPlease enter a different custom location name.");
	}

iFlightPlannerMap.prototype.reshowCustomLocations =
	function()
	{
		for (var i=0; i<this.customLocationMarkers.length; i++)
			this.customLocationMarkers[i].setMap(this.map);
	}

iFlightPlannerMap.prototype.saveCustomLocation = 
	function()
	{
		//validate
		if (Page_ClientValidate("SaveCustomLocation"))
		{
			var name = document.getElementById(this.id + "_CustomLocationFields_CustomLocationName").value;
			var desc = document.getElementById(this.id + "_CustomLocationFields_CustomLocationDesc").value;
			var latLng = this.lastLatLng;
			var result;
			
			//save location
			iFlightPlannerServices.SaveCustomLocation(name, desc, latLng.lng(), latLng.lat(), iFlightPlannerServices_SaveCustomLocation_SucceededCallback);
		}
	}

iFlightPlannerMap.prototype.showCustomLocationDialog =
	function()
	{
		//immediately hide menu
		this.hideContextMenu();
	
		//set lon and lat values
		var latLng = this.lastLatLng;
		var latEl = document.getElementById(this.id + "_CustomLocationLat");
		var lngEl = document.getElementById(this.id + "_CustomLocationLon");
		if (latEl && lngEl)
		{
			latEl.firstChild.nodeValue = this.formatLatLngForDMST(latLng.lat(),true);
			lngEl.firstChild.nodeValue = this.formatLatLngForDMST(latLng.lng(),false);
		}
				
		//show dialog		
		modalDialog_toggle(this.id + "_CustomLocationDialog",true,this.id + "_CustomLocationFields_CustomLocationName");
	}

iFlightPlannerMap.prototype.toggleCustomLocations =
	function()
	{
		var field = document.getElementById(this.id + "_CustomLocations");
		if (field)
		{
			if (field.checked)
				this.getCustomLocations();
			else
				this.clearCustomLocations();
		}
		else
			this.getCustomLocations();
	}

/**********************/
/**********************/
/* Favorites Methods */
iFlightPlannerMap.prototype.clearFavorites = 
	function()
	{
		//clear previous favorites markers
		for (var i=0; i<this.favoriteMarkers.length; i++)
		{
			this.favoriteMarkers[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.favoriteMarkers[i]);
			this.markers[this.favoriteMarkers[i].UniqueID] = null;
		}
		this.favoriteMarkers.splice(0,this.favoriteMarkers.length);		
	}

iFlightPlannerMap.prototype.getFavorites = 
	function()
	{
		MapsService.GetFavoriteAirports(MapsService_GetFavorites_SucceededCallback);
	}

iFlightPlannerMap.prototype.receiveFavorites =
	function(result)
	{
		var locations = jsonParse(result);
		var marker;
		
		//create marker for each custom location
		for (var i=0; i<locations.length; i++)
		{
			marker = this.createMarker(locations[i],false,this.markerType.FAVORITE_AIRPORT,91,true);
			if (marker)
				this.favoriteMarkers[this.favoriteMarkers.length] = marker;
		}		
	}
	
iFlightPlannerMap.prototype.setBoundsFromFavorites = 
	function()
	{
		var bounds = new google.maps.LatLngBounds();
		var isExtended = false;
		
		//extend bounds to all Favorite Airports and Custom Locations
		if (this.customLocationMarkers.length)
		{
			isExtended = true;
			for (var i=0; i<this.customLocationMarkers.length; i++)
				bounds.extend(this.customLocationMarkers[i].getPosition());
		}
		if (this.favoriteMarkers.length)
		{
			isExtended = true;
			for (var i=0; i<this.favoriteMarkers.length; i++)
				bounds.extend(this.favoriteMarkers[i].getPosition());
		}
		
		if (isExtended)
			this.map.fitBounds(bounds);
	}	
	
iFlightPlannerMap.prototype.toggleFavorites =
	function()
	{
		var field = document.getElementById(this.id + "_Favorites");
		if (field)
		{
			if (field.checked)
				this.getFavorites();
			else
				this.clearFavorites();
		}
		else
			this.getFavorites();
	}

/**********************/
/**********************/
/* Info Windows */
iFlightPlannerMap.prototype.showInfoWindow =
	function(e,infoWindow)
	{
		infoWindow.setPosition(e.latLng);
		infoWindow.open(this.map);
	}

iFlightPlannerMap.prototype.showMarkerInfo =
	function(latLng,title,type)
	{
		var content = "<div class=\"GmInfo\">";
		content += "<div class=\"Title\">" + title + "</div>";
		content += "<div class=\"Label\">Type</div><div class=\"Item\">" + type + "&nbsp;</div>";
		content += "</div>";

		var infoWindow = new google.maps.InfoWindow({content: content, position: latLng});
		infoWindow.open(this.map);	
	}

/**********************/
/**********************/
/* Lat/Lng Methods */
iFlightPlannerMap.prototype.calculateLatLngDistance =
	function(latLng1,latLng2,convertToKilometers,convertToNauticalMiles)
	{
		return this.calculateLatLngDistance2(latLng1.lat(),latLng1.lng(),latLng2.lat(),latLng2.lng(),convertToKilometers,convertToNauticalMiles);
	}

iFlightPlannerMap.prototype.calculateLatLngDistance2 =
	function(lat1,lng1,lat2,lng2,convertToKilometers,convertToNauticalMiles)
	{
		var theta, dist;
		
		//convert to miles
		theta = lng1 - lng2;
		dist = Math.sin(this.convertDegreesToRadians(lat1)) * Math.sin(this.convertDegreesToRadians(lat2)) + Math.cos(this.convertDegreesToRadians(lat1)) * Math.cos(this.convertDegreesToRadians(lat2)) * Math.cos(this.convertDegreesToRadians(theta));
		dist = Math.acos(dist);
		dist = this.convertRadiansToDegrees(dist);
		dist = dist * 60 * 1.1515;
		
		//get correct unit
		if (convertToKilometers)
			dist = dist * 1.609344;
		else if (convertToNauticalMiles)
			dist = dist * 0.8684;
	
		return dist;
	}
	
iFlightPlannerMap.prototype.convertDegreesToRadians =
	function(degrees)
	{	
		return (degrees * Math.PI / 180);
	}

iFlightPlannerMap.prototype.convertLatLngFromDMST =
	function(d,m,s,t)
	{
		return d + (m/60) + (s/3600) + (t/36000);
	}
		
iFlightPlannerMap.prototype.convertLatLngToDMST =
	function(value)
	{
		var n, d, m, s, t;
		
		if (value < 0)
		{
			value = Math.abs(value)
			n = true;
		}
		else
			n = false;
		
		//determine values
		d = Math.floor(value);
		value -= d;
		value *= 60;
		m = Math.floor(value);
		value -= m;
		value *= 60;
		s = Math.floor(value);
		value -= s;
		value *= 10;
		t = Math.floor(value);
		
		return {negative: n, degrees: d, minutes: m, seconds: s, tenths: t};
	}	
	
iFlightPlannerMap.prototype.convertRadiansToDegrees =
	function(radians)
	{	
		return (radians / Math.PI * 180);
	}
	
iFlightPlannerMap.prototype.convertUSDigitStringToLatLng =
	function(value,isLng)
	{
		var d, m, s, t;
		var result = 0;
		
		if (value && value.length >= 2 && value.length <= 8 && value.length != 6)
		{
			if (value.length == 2 || value.length == 3)
				d = parseInt(value);
			else if (value.length == 4)
			{
				d = parseInt(value.substring(0, 2));
				m = parseInt(value.substring(2, 4));
			}
			else if (value.length == 5)
			{
				d = parseInt(value.substring(0, 3));
				m = parseInt(value.substring(3, 5));
			}
			else if (value.length == 7)
			{
				d = parseInt(value.substring(0, 2));
				m = parseInt(value.substring(2, 4));
				s = parseInt(value.substring(4, 6));
				t = parseInt(value.substring(6, 7));
			}
			else if (value.length == 8)
			{
				d = parseInt(value.substring(0, 3));
				m = parseInt(value.substring(3, 5));
				s = parseInt(value.substring(5, 7));
				t = parseInt(value.substring(7, 8));
			}	
			
			result = this.convertLatLngFromDMST(d,m,s,t);
			if (isLng) result *= -1;			
		}
		
		return result;
	}

iFlightPlannerMap.prototype.convertUSDigitStringToLatLngPair =
	function(value)
	{
		var re = /([0-9]{2,8})\/([0-9]{2,8})/;
		var latText, lngText;
		var lat, lng;
		var latLng = null;
		
		if (re.test(value))
		{
			var parts = re.exec(value);
			latText = parts[1]; 
			lngText = parts[2];
			if (latText.length != 6 && lngText.length != 6)
			{
				lat = this.convertUSDigitStringToLatLng(parts[1],false);
				lng = this.convertUSDigitStringToLatLng(parts[2],true);
				latLng = new google.maps.LatLng(lat,lng);
			}				
		}
		
		return latLng;
	}

iFlightPlannerMap.prototype.formatLatLngForDMST =
	function(value, isLat)
	{
		var parts = this.convertLatLngToDMST(value);
		var text;
		
		if (parts.degrees >= 10)
			text = parts.degrees + "°";
		else
			text = "0" + parts.degrees + "°";
		if (parts.minutes >= 10)
			text += parts.minutes + "'";
		else
			text += "0" + parts.minutes + "'";
		if (parts.seconds >= 10)
			text += parts.seconds;
		else
			text += "0" + parts.seconds;
		if (parts.tenths > 0) text += "." + parts.tenths;
		text += "\" ";
		
		if (isLat)
			text += (parts.negative ? "S" : "N");
		else
			text += (parts.negative ? "W" : "E");
				
		return text;
	}
	
iFlightPlannerMap.prototype.formatLatLngPairForDMST =
	function(latLng,separator)
	{
		if (!separator) separator = " ";
		return this.formatLatLngForDMST(latLng.lat(),true) + separator + this.formatLatLngForDMST(latLng.lng(),false);
	}	

iFlightPlannerMap.prototype.formatLatLngToUSDigitString =
	function(latLng)
	{
		var latValue, lngValue;
		var lat, lng;
		var text;
		
		latValue = latLng.lat();
		lngValue = latLng.lng();
		
		//convert to degrees, minutes, seconds, tenths
		lat = this.convertLatLngToDMST(latValue);
		lng = this.convertLatLngToDMST(lngValue);
	
		//set lat text
		text = lat.degrees.toString();
		if (lat.minutes > 0 || lat.seconds > 0 || lat.tenths > 0)
		{
			if (lat.minutes >= 10)
				text += lat.minutes.toString();
			else
				text += "0" + lat.minutes.toString();
		}
		if (lat.seconds > 0 || lat.tenths > 0)
		{
			if (lat.seconds >= 10)
				text += lat.seconds.toString();
			else
				text += "0" + lat.seconds.toString();
				
			text += lat.tenths.toString();
		}

		//add /
		text += "/"

		//add lon value
		text += lng.degrees.toString();
		if (lng.minutes > 0 || lng.seconds > 0 || lng.tenths > 0)
		{
			if (lng.minutes >= 10)
				text += lng.minutes.toString();
			else
				text += "0" + lng.minutes.toString();
		}
		if (lng.seconds > 0 || lng.tenths > 0)
		{
			if (lng.seconds >= 10)
				text += lng.seconds.toString();
			else
				text += "0" + lng.seconds.toString();
				
			text += lng.tenths.toString();
		}
		
		return text;
	}
	
iFlightPlannerMap.prototype.getPixelCoordForLatLng =
	function (latLng)
	{
		var map = this.map;
		var scale = Math.pow(2, map.getZoom());
		var bounds = map.getBounds();
		var nw = new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getSouthWest().lng());
		var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
		var worldCoordinate = map.getProjection().fromLatLngToPoint(latLng);
		var pixelOffset = new google.maps.Point(
			Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
			Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
			);
		return pixelOffset;
	}

/**********************/
/**********************/
/* Locations (show Adjacent for Right Click on map) */
iFlightPlannerMap.prototype.createAdjacentLocationsTable =
	function(descriptor,locations,cls,isFromDrag)
	{
		var container, el;
		var html, location;
		var count = 0;
	
		//get container and div element
		container = document.getElementById(this.id + "_Insert" + descriptor + "Block");
		el = document.getElementById(this.id + "_Insert" + descriptor + "Grid");
	
		//create location options
		html = "<table class=\"GridTable\">";
		//html += "<tr class=\"GridHeaderRow\"><th class>Use</th><th>Type</th><th>ID</th><th>Distance</th><th>Options</th></tr>";
		if (locations.length) 
		{
			for (var i=0; i<locations.length; i++)
			{
				//check location class
				location = locations[i];
				if (cls.indexOf(location.Class) >= 0)
				{
					locationInfo = location.UniqueID + ",'" + this.escapeQuotes(location.BestID) + "','" + this.escapeQuotes(location.Name) + "','" + this.escapeQuotes(location.Class) + "','" + this.escapeQuotes(location.Type) + "','" + this.escapeQuotes(location.Use) + "'," + location.Lat + "," + location.Lng
					html += "<tr class=\"GridRow\">";
					
					//Use
					if (location.Use && location.Use != "" && location.Use != "None")
						html += "<td class=\"Use\"><img src=\"" + this.imgRoot + "Graphics/Labels/" + location.Use + ".png\" alt=\"" + location.Use + "\" title=\"" + location.Use + "\" /></td>";
					
					//Type
					if (location.Class == "A" && location.Type != "")
						html += "<td class=\"Type\"><img src=\"" + this.imgRoot + "Graphics/Labels/" + location.Type.replace(" ","") + ".png\" alt=\"" + location.Type + "\" title=\"" + location.Type + "\" /></td>";
					else if (location.Class == "C")
						html += "<td class=\"TypeWide\"><img src=\"" + this.markersBaseUrl + "C.png\" alt=\"Custom Location\" title=\"Custom Location\" /></td>";
					else if (location.Class != "L")
						html += "<td class=\"TypeWide\">" + location.Type + "</td>";	
					else
						html += "<td class=\"TypeWide\">Lat/Lng</td>";
					
					//ID & Name
					if (location.Class != "L")
						html += "<td class=\"ObjectName\"><div>" + location.BestID + "</div><div class=\"CellFinePrint\">" + location.Name + "</div></td>";
					else
						html += "<td class=\"ObjectName\"><div>" + location.Name + "</div></td>";
					
					//Distance
					html += "<td class=\"CellFinePrint\">" + location.Distance + " SM</td>";
					
					//Actions
					html += "<td class=\"ActionsInline\">";
					if (this.routesEnabled)
					{
						if (!isFromDrag)
							html += "<a href=\"javascript:ifpMap.addRouteMarkerFromDialog(" + locationInfo + ");\"><img src=\"" + this.imgRoot + "Shared/Graphics/Icons/MapGo.png\" alt=\"\" /> Select</a>";
						else
							html += "<a href=\"javascript:ifpMap.snapRouteMarkerTo(" + locationInfo + ");\"><img src=\"" + this.imgRoot + "Shared/Graphics/Icons/MapGo.png\" alt=\"\" /> Select</a>";
					}
					html += "<a class=\"NoBorder\" href=\"javascript:ifpMap.getLocationInfoForUniqueID('" + location.UniqueID + "');\"><img src=\"" + this.imgRoot + "Shared/Graphics/Icons/View.png\" alt=\"+\" />" + (!this.routesEnabled ? "&nbsp;Info" : "") + "</a>";
					html += "</td>";
					html += "</tr>";
					
					//increment count
					count += 1;
				} 
			}
		}
		
		//finalize html, set as innerHtml property
		html += "</table>";
		el.innerHTML = html;
		
		//show/hide container
		container.style.display = (count ? "block" : "none");
		
		return count;
	}

iFlightPlannerMap.prototype.getAdjacentLocations =
	function(lat,lng,zoom,radius)
	{
		//hide context menu
		this.hideContextMenu();
	
		if (!radius)
		{
			switch (zoom)
			{
				 case 1: radius = .25; break;
				 case 2: radius = .22; break;
				 case 3: radius = .19; break;
				 case 4: radius = .16; break;
				 case 5: radius = .14; break;
				 case 6: radius = .12; break;
				 case 7: radius = .10; break;
				 case 8: radius = .09; break;
				 case 9: radius = .08; break;
				 case 10: radius = .07; break;
				 case 11: radius = .06; break;
				 case 12: radius = .05; break;
				 case 13: radius = .05; break;
				 default: radius = .25;
			}
		}
		MapsService.GetAdjacentLocations(lat,lng,radius,MapsService_GetAdjacentLocations_SucceededCallback,null);
	}

iFlightPlannerMap.prototype.getAdjacentLocationsFromLocationInfo =
	function(lat,lng,zoom,radius)
	{
		//close dialog first
		modalDialog_toggle(this.id + "_LocationDialog",false);
		
		//call getAdjacent
		this.getAdjacentLocations(lat,lng,zoom,radius);
	}

iFlightPlannerMap.prototype.receiveAdjacentLocations =
	function(result)
	{
		var locations = this.parseLocations(result);
		
		//show dialog
		this.showAdjacentLocationsDialog(locations);
	}
	
iFlightPlannerMap.prototype.showAdjacentLocationsDialog =
	function(locations,useContextMenuMarker)
	{
		var html, el, value;
		var location, locationInfo, marker;
		var latLng;
		var re = /[0-9]{2,8}\/[0-9]{2,8}/;
		var useSnap = false;
		var count = 0;
		var routeParts, hasRoute;
		var hasFrom, hasTo;
		
		//if useContextMenuMarker, create list with only 1 location
		if (useContextMenuMarker)
		{
			//hide menu and dialog
			this.hideContextMenu();
			modalDialog_toggle(this.id + "_LocationDialog",false);
		
			//create location list from marker
			marker = this.contextMenuMarker;
			if (marker)
				location = this.createLocationObject(marker.UniqueID,marker.BestID,marker.ID,marker.Name,marker.Class,marker.Type,marker.Use,marker.getPosition().lat(),marker.getPosition().lng());
			else //use lat lon
			{
				value = this.formatLatLngToUSDigitString(this.lastLatLng);
				location = this.createLocationObject(0,value,value,this.formatLatLngPairForDMST(this.lastLatLng," "),"L","Latitude/Longitude","",this.lastLatLng.lat(),this.lastLatLng.lng());
			}
			
			//create locations array
			locations = new Array();
			locations[locations.length] = location;
		}
		
		//determine whether to show Insert or Snap options
		useSnap = (this.contextMenuMarker && this.contextMenuMarker.Class && this.contextMenuMarker.Class == "L");
		
		//get current route
		routeParts = this.getRouteParts();
		
		//add From and To locations, if present
		if (!this.standalone)
		{
			//add From field
			el = document.getElementById(this.fromFieldID);
			if (el && el.value.length)
			{
				routeParts.splice(0,0,el.value);
				hasFrom = true;
			}
			
			//add To field
			el = document.getElementById(this.toFieldID);
			if (el && el.value.length)
			{
				routeParts[routeParts.length] = el.value;
				hasTo = true;
			}
		}
		
		//set flag
		hasRoute = (routeParts.length > 0);
				
		//if standalone, add Start of Route option
		if (this.standalone || !hasRoute || !hasFrom)
			html = "<div class=\"GmInsertOption\"><input value=\"0\" id=\"" + this.id + "_Insert_Start\" name=\"" + this.id + "_Insert_AtValues\" type=\"radio\" " + (hasRoute ? "" : "checked=\"checked\"") + "/><label for=\"" + this.id + "_Insert_Start\">Start of Route</label></div>";
		else
			html = "";
		
		//add options
		if (hasRoute)
		{
			for (var i=0; i<routeParts.length; i++)
			{
				value = routeParts[i];
				if (re.test(value))
				{
					value = this.formatLatLngPairForDMST(this.convertUSDigitStringToLatLngPair(value), "<br />");
					html += "<div class=\"GmInsertWaypoint GmInsertWaypointLL\">" + value + "</div>";
				}
				else
					html += "<div class=\"GmInsertWaypoint\">" + value + "</div>";
				
				if (this.standalone || i < routeParts.length - 1 || !hasTo)
					html += "<div class=\"GmInsertOption\"><input type=\"radio\" value=\"" + (i + 1).toString() + "\" id=\"" + this.id + "_Insert_" + i + "\" name=\"" + this.id + "_Insert_AtValues\" /><label for=\"" + this.id + "_Insert_" + i + "\">" + ((i<routeParts.length-1) ? "Insert Here":"End of Route") + "</label></div>";
			}
		}
		document.getElementById(this.id + "_Insert_At").innerHTML = html;
		
		//add custom locations, airports, VORs, fixes
		count = this.createAdjacentLocationsTable("LatLng",locations,"L",useSnap);
		count += this.createAdjacentLocationsTable("CustomLocations",locations,"C",useSnap);
		count += this.createAdjacentLocationsTable("Airports",locations,"A",useSnap);
		count += this.createAdjacentLocationsTable("VORs",locations,"V",useSnap);
		count += this.createAdjacentLocationsTable("Fixes",locations,"F",useSnap);
		
		//show/hide Insert fields
		el = document.getElementById(this.id + "_InsertAt");
		if (el) el.style.display = (useSnap ? "none" : "block");
		el = document.getElementById(this.id + "_InsertLocations");
		if (el) el.className = (useSnap ? "GmInsertLocationsW2" : "GmInsertLocationsW");
		
		//if necessary, show no locations message
		el = document.getElementById(this.id + "_InsertNoLocations");
		if (el) el.style.display = (count == 0 ? "block" : "none"); 
		
		//show dialog
		modalDialog_toggle(this.id + "_LocationAddDialog",true);
	}


/**********************/
/**********************/
/* Locations (show info for marker click on map) */
iFlightPlannerMap.prototype.createLocationObject =
	function(uniqueID,bestID,id,name,cls,type,usevalue,lat,lng)
	{
		var location = 
			{
				UniqueID: uniqueID,
				BestID: bestID,
				ID: id,
				Name: name,
				Class: cls,
				Type: type,
				Use: usevalue,
				Lat: lat,
				Lng: lng,
				Distance: 0
			};
		return location;
	}

iFlightPlannerMap.prototype.getLocationInfo =
	function(marker)
	{
		var markerToUse;
		
		//determine which marker to use
		if (!marker)
			marker = this.contextMenuMarker;
		else
			this.contextMenuMarker = marker;		
	
		if (marker)
		{
			if (marker.Class != "L" && marker.UniqueID)
				this.getLocationInfoForUniqueID(marker.UniqueID,true);
			else if (marker.Class == "L")
				this.showLocationInfoForLatLngMarker(marker);
		}
	}

iFlightPlannerMap.prototype.getLocationInfoForUniqueID =
	function(uniqueID,fromMarker)
	{
		//hide context menu
		this.hideContextMenu(!fromMarker);
		MapsService.GetLocationInfo(uniqueID,MapsService_GetLocationInfo_SucceededCallback);	
	}
	
iFlightPlannerMap.prototype.receiveLocationInfo =	
	function(result)
	{
		var location;
		
		if (result && result.length)
		{
			//parse result
			location = jsonParse(result);
			this.showLocationInfo(location);
		}
	}

iFlightPlannerMap.prototype.showLocationInfo =	
	function(location)
	{
		var id, name, cityState;
		var isAirport;
		var isInRoute;
	
		if (location)
		{
			//determine ID text
			if (location.Class != "L")
			{
				if (location.BestID != location.ID)
					id = location.BestID + " (" + location.ID + ")";
				else
					id = location.BestID;
			}
			else
				id = location.Name;
			
			//determine descriptive text
			if (location.Class == "A")
			{	
				//name
				name = location.Name;
				
				//city, state
				if (location.City && location.City.length)
					cityState = location.City;
				if (location.State && location.State.length)
				{
					if (cityState && cityState.length)
						cityState += ", " + location.State;
					else
						cityState = location.State;
				}
				isAirport = true;
				
				//add hit track
				try {
					pageTracker._trackPageview('/AirportInfo/' + id + '/');
				} catch(err) {}
			}
			else if (location.Class == "C")
			{
				name = (location.Name && location.Name.length ? location.Name : ".");
				cityState = "Custom Location";
				isAirport = false;
			}
			else
			{
				name = location.Type;
				cityState = "";
				isAirport = false;
			}
			
			//determine if in route
			isInRoute = this.isLocationInRoute(location.BestID);
			
			//set html
			document.getElementById(this.id + "_LocCode").firstChild.nodeValue = id;
			document.getElementById(this.id + "_LocCityState").firstChild.nodeValue = cityState;
			document.getElementById(this.id + "_LocName").firstChild.nodeValue = name;
			document.getElementById(this.id + "_LocLatLng").innerHTML = this.formatLatLngForDMST(location.Lat,true) + "&nbsp;&nbsp;&nbsp;" + this.formatLatLngForDMST(location.Lng,false);
			
			//add/remove from route
			if (this.routesEnabled)
			{
				//document.getElementById(this.id + "_LocAddLink").href = "javascript:ifpMap.getAdjacentLocationsFromLocationInfo(" + location.Lat + "," + location.Lng + ",0,.01);";
				if (!isInRoute)
					document.getElementById(this.id + "_LocAddLink").href = "javascript:ifpMap.showAdjacentLocationsDialog(null,true);";
				else
					document.getElementById(this.id + "_LocRemoveLink").href = "javascript:ifpMap.removeRouteMarker();";
				
				//show/hide appropriate link
				document.getElementById(this.id + "_LocAddLinkWrapper").style.display = (isInRoute ? "none" : "");
				document.getElementById(this.id + "_LocRemoveLinkWrapper").style.display = (isInRoute && this.contextMenuMarker ? "" : "none");
			}
			else
			{
				document.getElementById(this.id + "_LocAddLinkWrapper").style.display = "none";
				document.getElementById(this.id + "_LocRemoveLinkWrapper").style.display = "none";
			}
			
			document.getElementById(this.id + "_LocCenterLink").href = "javascript:ifpMap.setCenter(" + location.Lat + "," + location.Lng + ");";
			document.getElementById(this.id + "_LocZoomLink").href = "javascript:ifpMap.setCenter(" + location.Lat + "," + location.Lng + ");ifpMap.setZoom(10);";
			document.getElementById(this.id + "_LocAFDLinkWrapper").style.display = (isAirport ? "inline" : "none"); 
			document.getElementById(this.id + "_LocAFDLink").href = this.afdUrl + "?ID=" + location.BestID;
			document.getElementById(this.id + "_LocMetarTafWrapper").style.display = (isAirport ? "block" : "none"); 
			document.getElementById(this.id + "_LocMetar").innerHTML = location.METAR;
			document.getElementById(this.id + "_LocTaf").innerHTML = location.TAF;
			
			//show dialog
			modalDialog_toggle(this.id + "_LocationDialog",true);
		}
	}
	
iFlightPlannerMap.prototype.showLocationInfoForLatLngMarker =
	function(marker)
	{
		var location;
		var latLng = marker.getPosition();
		var id = this.formatLatLngToUSDigitString(latLng);
	
		//create location
		location = this.createLocationObject(0,id,id,this.formatLatLngPairForDMST(latLng),"L","Latitude/Longitude","",latLng.lat(),latLng.lng());
		
		//hide menu, show location info
		this.hideContextMenu();
		this.showLocationInfo(location);		
	}
	
/**********************/
/**********************/
/* Location Context Menus */
iFlightPlannerMap.prototype.centerToLocation =
	function(zoom)
	{
		this.map.setCenter(this.lastLatLng);
		if (zoom)
			this.map.setZoom(10);
	}

iFlightPlannerMap.prototype.hideContextMenu =
	function(clearMarker)
	{
		var menu = document.getElementById(this.id + "_LocationMenu");
		if (menu) menu.style.display = "none";
		if (clearMarker) this.contextMenuMarker = null;
	}

iFlightPlannerMap.prototype.showContextMenu =
	function(event,marker,useSnap)
	{
		if (this.routesEnabled)
		{
			var menu = document.getElementById(this.id + "_LocationMenu");
			if (menu)
			{
				var bestID, title, name;
				var latLng;
				var isAirport = false;
				var isCustomLoc = false;
				var isLatLng = false;
				var isGeneric = false;
				var isNavaid = false;
				var x, y;
				var coords;
				var li, el, inRoute;
				
				//customize menu
				if (marker)
				{
					bestID = marker.BestID;
					title = marker.BestID;
					name = marker.Name;
					
					isAirport = (marker.Class == "A");
					isCustomLoc = (marker.Class == "C");
					isNavaid = (marker.Class == "F" || marker.Class == "V");
					isLatLng = (marker.Class == "L");				
					inRoute = (marker.MarkerType == this.markerType.ROUTE_AIRPORT || marker.MarkerType == this.markerType.ROUTE_MARKER);
					
					//determine x,y (add offset for non-airport markers)
					latLng = marker.getPosition();
					coords = this.getPixelCoordForLatLng(latLng);
					x = coords.x;
					y = coords.y;
					if (!isAirport)
					{
						x += 6;
						y += 6;
					}				
					
					//set airport links
					if (isAirport)
					{
						//"Set As" links
						if (!this.standalone)
						{
							el = document.getElementById(this.id + "_Location_SetFromLink");
							if (el) el.href = "javascript:ifpMap.setAirport('F','" + marker.BestID + "');"
							el = document.getElementById(this.id + "_Location_SetToLink");
							if (el) el.href = "javascript:ifpMap.setAirport('T','" + marker.BestID + "');"
							el = document.getElementById(this.id + "_Location_SetAltLink");
							if (el) el.href = "javascript:ifpMap.setAirport('A','" + marker.BestID + "');"
						}
						
						//A/FD link
						el = document.getElementById(this.id + "_Location_AFDLink");
						if (el) el.href = this.afdUrl + "?ID=" + marker.BestID;
					}
					else if (isLatLng)
					{
						title = "Latitude/Longitude";
						name = "";
					}
					
					show = true;
				}
				else if (event)
				{
					bestID = this.formatLatLngToUSDigitString(event.latLng);
					title = "Location Options";
					name = "";
					latLng = event.latLng;
					isGeneric = true;
				
					//set x,y
					x = event.pixel.x;
					y = event.pixel.y;
				
					//determine if point is in route
					inRoute = this.isLocationInRoute(bestID);
				
					show = true;
				}		

				if (show)
				{
					//set last LatLng property
					this.lastLatLng = latLng;
					this.contextMenuMarker = marker;
					this.contextUseSnap = useSnap;
					
					//set values
					document.getElementById(this.id + "_LocationTitle").firstChild.nodeValue = title;
					document.getElementById(this.id + "_LocationName").firstChild.nodeValue = name;
					document.getElementById(this.id + "_LocationName").style.display = (name && name.length ? "block" : "none");
				
					document.getElementById(this.id + "_LocationMenuLat").firstChild.nodeValue = this.formatLatLngForDMST(latLng.lat(),true);
					document.getElementById(this.id + "_LocationMenuLon").firstChild.nodeValue = this.formatLatLngForDMST(latLng.lng(),false);
				
					//show/hide add/remove options
					
					document.getElementById(this.id + "_LocationAddOptions").style.display = (inRoute ? "none" : "block");
					document.getElementById(this.id + "_LocationRemoveOptions").style.display = (inRoute ? "block" : "none");
					
					//show/hide option sets
					document.getElementById(this.id + "_LocationSetAsOptions").style.display = ((isAirport && !this.standalone) ? "block" : "none");
					//document.getElementById(this.id + "_LocationAddRemoveOptions").style.display = ((isAirport || isCustomLoc || isNavaid) ? "block" : "none");
					document.getElementById(this.id + "_LocationAOptions").style.display = (isAirport ? "block" : "none");
					document.getElementById(this.id + "_LocationCFVOptions").style.display = (isCustomLoc || isNavaid ? "block" : "none");
					document.getElementById(this.id + "_LocationLOptions").style.display = (isLatLng ? "block" : "none");
					document.getElementById(this.id + "_LocationGenericOptions").style.display = (isGeneric ? "block" : "none");
				
					//set menu location
					menu.style.top = y + "px";
					menu.style.left = x + "px";
								
					//show menu
					menu.style.display = "block";				
				}
				else
					this.hideContextMenu();
			}
		}
	}

/**********************/
/**********************/
/* Map Types */
iFlightPlannerMap.prototype.addMapLabels =
	function()
	{
		var tileHybridLabels = new google.maps.ImageMapType(
			{
				getTileUrl:
					function(ll, z) 
					{
						var X = ll.x % (1 << z); // wrap
						return "http://mt0.google.com/vt/v=apt.116&hl=en-US&x="	+ X + "&y=" + ll.y + "&z=" + z + "&s=G&lyrs=h";
					},
				tileSize: new google.maps.Size(256, 256),
				isPng: true,
				maxZoom: 20,
				name: "lyrs=h",
				alt: "Hybrid labels"
			}); 
		
		//add overlay
		map.overlayMapTypes.insertAt(1, tileHybridLabels);
	}

iFlightPlannerMap.prototype.getIFRHighMapOptions = 
	function()
	{
		var options = 
		{
			 getTileUrl:
				function(coord, zoom)
				{
					var url;
					if (ifpMap)
					{
						if ( zoom == 1 && coord.x == 0 && coord.y == 0 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 2 && coord.x >= 0 && coord.x <= 1 && coord.y == 1)
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 3 && coord.x >= 0 && coord.x <= 2 && coord.y >= 2 && coord.y <= 3 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 4 && coord.x >= 1 && coord.x <= 5 && coord.y >= 4 && coord.y <= 7 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 5 && coord.x >= 3 && coord.x <= 11 && coord.y >= 9 && coord.y <= 14 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 6 && coord.x >= 7 && coord.x <= 22 && coord.y >= 19 && coord.y <= 29 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 7 && coord.x >= 14 && coord.x <= 44 && coord.y >= 38 && coord.y <= 59 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 8 && coord.x >= 28 && coord.x <= 89 && coord.y >= 77 && coord.y <= 118 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 9 && coord.x >= 71 && coord.x <= 171 && coord.y >= 158 && coord.y <= 231 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else if ( zoom == 10 && coord.x >= 142 && coord.x <= 342 && coord.y >= 316 && coord.y <= 463 )
							return ifpMap.tilesBaseUrl + "IFRHigh/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
						else
							return "";
					}
					else
						return "";
				},
			tileSize: new google.maps.Size(256, 256),
			isPng: true,
			minZoom: 1,
			maxZoom: 10,
			name: "IFR High"
		}
		
		return options;
	}

iFlightPlannerMap.prototype.getIFRLowMapOptions = 
	function()
	{
		var options = 
		{
			 getTileUrl:
				function(coord, zoom)
				{
					if ( zoom == 1 && coord.x == 0 && coord.y == 0 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 2 && coord.x >= 0 && coord.x <= 1 && coord.y == 1)
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 3 && coord.x >= 0 && coord.x <= 2 && coord.y >= 2 && coord.y <= 3 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 4 && coord.x >= 1 && coord.x <= 5 && coord.y >= 4 && coord.y <= 7 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 5 && coord.x >= 3 && coord.x <= 11 && coord.y >= 9 && coord.y <= 14 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 6 && coord.x >= 7 && coord.x <= 22 && coord.y >= 19 && coord.y <= 29 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 7 && coord.x >= 15 && coord.x <= 44 && coord.y >= 39 && coord.y <= 59 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 8 && coord.x >= 31 && coord.x <= 88 && coord.y >= 79 && coord.y <= 119 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 9 && coord.x >= 63 && coord.x <= 177 && coord.y >= 158 && coord.y <= 239 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 10 && coord.x >= 127 && coord.x <= 355 && coord.y >= 316 && coord.y <= 478 )
						return ifpMap.tilesBaseUrl + "IFRLow/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";	
					else
						return "";
				},
			tileSize: new google.maps.Size(256, 256),
			isPng: true,
			minZoom: 1,
			maxZoom: 10,
			name: "IFR Low"
		}
		
		return options;
	}
	
iFlightPlannerMap.prototype.getIFRLowAreaMapOptions = 
	function()
	{
		var options = 
		{
			 getTileUrl:
				function(coord, zoom)
				{
					if ( zoom == 3 && coord.x >= 1 && coord.x <= 2 && coord.y >= 2 && coord.y <= 3 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 4 && coord.x >= 2 && coord.x <= 4 && coord.y >= 5 && coord.y <= 6 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 5 && coord.x >= 5 && coord.x <= 9 && coord.y >= 11 && coord.y <= 13 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 6 && coord.x >= 10 && coord.x <= 18 && coord.y >= 22 && coord.y <= 27 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 7 && coord.x >= 20 && coord.x <= 37 && coord.y >= 45 && coord.y <= 54 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 8 && coord.x >= 40 && coord.x <= 75 && coord.y >= 91 && coord.y <= 109 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 9 && coord.x >= 80 && coord.x <= 151 && coord.y >= 182 && coord.y <= 219 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 10 && coord.x >= 161 && coord.x <= 303 && coord.y >= 364 && coord.y <= 438 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 11 && coord.x >= 323 && coord.x <= 607 && coord.y >= 728 && coord.y <= 877 )
						return ifpMap.tilesBaseUrl + "IFRLowArea/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
        		},
			tileSize: new google.maps.Size(256, 256),
			isPng: true,
			minZoom: 3,
			maxZoom: 11,
			name: "IFR Low Area"
		}
		
		return options;
	}

iFlightPlannerMap.prototype.getSectionalMapOptions = 
	function()
	{
		var options = 
		{
			 getTileUrl:
				function(coord, zoom)
				{
					var url;
					if ( zoom == 1 && coord.x == 0 && coord.y == 0 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 2 && coord.x >= 0 && coord.x <= 1 && coord.y == 1)
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 3 && coord.x >= 0 && coord.x <= 2 && coord.y >= 2 && coord.y <= 3 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 4 && coord.x >= 0 && coord.x <= 5 && coord.y >= 5 && coord.y <= 7 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 5 && coord.x >= 1 && coord.x <= 10 && coord.y >= 10 && coord.y <= 15 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 6 && coord.x >= 2 && coord.x <= 21 && coord.y >= 20 && coord.y <= 31 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 7 && coord.x >= 5 && coord.x <= 42 && coord.y >= 41 && coord.y <= 62 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 8 && coord.x >= 10 && coord.x <= 84 && coord.y >= 82 && coord.y <= 124 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 9 && coord.x >= 21 && coord.x <= 169 && coord.y >= 165 && coord.y <= 248 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 10 && coord.x >= 42 && coord.x <= 339 && coord.y >= 330 && coord.y <= 497 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else if ( zoom == 11 && coord.x >= 85 && coord.x <= 679 && coord.y >= 660 && coord.y <= 967 )
						return ifpMap.tilesBaseUrl + "Sectional/Z" + zoom + "/" + coord.y + "/" + coord.x + ".jpg";
					else
						return "";
				},
			tileSize: new google.maps.Size(256, 256),
			isPng: true,
			minZoom: 1,
			maxZoom: 11,
			name: "Sectional"
		}
		
		return options;
	}

iFlightPlannerMap.prototype.getTACMapOptions = 
	function()
	{
		var options = 
		{
			 getTileUrl:
				function(coord, zoom)
				{
					if ( zoom == 3 && coord.x >= 1 && coord.x <= 2 && coord.y >= 2 && coord.y <= 3 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 4 && coord.x >= 2 && coord.x <= 5 && coord.y >= 5 && coord.y <= 6 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 5 && coord.x >= 4 && coord.x <= 10 && coord.y >= 10 && coord.y <= 13 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 6 && coord.x >= 9 && coord.x <= 20 && coord.y >= 21 && coord.y <= 27 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 7 && coord.x >= 18 && coord.x <= 40 && coord.y >= 42 && coord.y <= 55 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 8 && coord.x >= 36 && coord.x <= 80 && coord.y >= 85 && coord.y <= 111 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 9 && coord.x >= 72 && coord.x <= 161 && coord.y >= 171 && coord.y <= 222 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 10 && coord.x >= 145 && coord.x <= 322 && coord.y >= 342 && coord.y <= 444 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
					else if ( zoom == 11 && coord.x >= 291 && coord.x <= 645 && coord.y >= 684 && coord.y <= 889 )
						return ifpMap.tilesBaseUrl + "TAC/Z" + zoom + "/" + coord.y + "/" + coord.x + ".png";
				},
			tileSize: new google.maps.Size(256, 256),
			isPng: true,
			minZoom: 3,
			maxZoom: 11,
			name: "TAC"
		}
		
		return options;
	}

iFlightPlannerMap.prototype.mapTypesChanged =
	function()
	{
		var mapType = this.map.getMapTypeId().toLowerCase();
		
		var isSectional = (mapType == "sectional" && this.map.getZoom() > 9);
		var isRoadMap = (mapType == "roadmap" || mapType == "terrain" || mapType == "hybrid");
		
		this.tacOption.style.display = (isSectional ? "block" : "none");
		this.trafficOption.style.display = (isRoadMap ? "block" : "none");
		this.controlsContainer.style.display = (isSectional || isRoadMap) ? "block" : "none";
		
		//show/hide TAC
		this.toggleTAC(!isSectional);
		
		//hide context menu
		this.hideContextMenu();		
	}
	
iFlightPlannerMap.prototype.toggleFreezing =
	function(fromClick)
	{
		var proceed = true;
		var field = document.getElementById(this.id + "_Freezing");
		if (field)
		{
			//if fromClick, show warning about line action
			if (fromClick && field.checked) proceed = confirm("WARNING: Due to a Google Maps limitation, turning on the Freezing Level overlay prevents further interaction with route lines, TFRs, AIRMETs and SIGMETs.\n\nDo you still wish to add the Freezing Level overlay to the map?");
			if (proceed)
			{
				//always kill previous SFC layer
				if (this.freezingLayer)
				{
					this.freezingLayer.setMap(null);
					this.freezingLayer = null;
				}
			
				//if field checked, get new SFC analysis
				if (field.checked)
				{
					this.freezingLayer = new google.maps.KmlLayer("http://www.wrh.noaa.gov/zse/gm/fzl0.kml", {preserveViewport: true});
					this.freezingLayer.setMap(this.map);
				}
			}
			else
				field.checked = false;	
		}
	}	
	
iFlightPlannerMap.prototype.toggleSfc =
	function(fromClick)
	{
		var proceed = true;
		var field = document.getElementById(this.id + "_Sfc");
		if (field)
		{
			//if fromClick, show warning about line action
			if (fromClick && field.checked) proceed = confirm("WARNING: Due to a Google Maps limitation, turning on the Surface Analysis overlay prevents further interaction with route lines, TFRs, AIRMETs and SIGMETs.\n\nDo you still wish to add the Surface Analysis overlay to the map?");
			if (proceed)
			{
				//always kill previous SFC layer
				if (this.sfcLayer)
				{
					this.sfcLayer.setMap(null);
					this.sfcLayer = null;
				}
			
				//if field checked, get new SFC analysis
				if (field.checked)
				{
					this.sfcLayer = new google.maps.KmlLayer("http://www.wrh.noaa.gov/zse/gm/sfc0.kml", {preserveViewport: true});
					this.sfcLayer.setMap(this.map);
				}
			}
			else
				field.checked = false;
		}
	}

iFlightPlannerMap.prototype.toggleTAC =
	function(forceRemove)
	{
		var field = document.getElementById(this.id + "_TAC");
		if (field && !forceRemove)
		{
			if (field.checked)
				this.map.overlayMapTypes.setAt(this.tacIndex,this.tacMap);
			else
				this.map.overlayMapTypes.setAt(this.tacIndex,null);	
		}
		
		if (forceRemove)
			this.map.overlayMapTypes.setAt(this.tacIndex,null);
	}
	
iFlightPlannerMap.prototype.toggleTraffic =
	function()
	{
		var field = document.getElementById(this.id + "_Traffic");
		if (field)
		{
			if (field.checked)
				this.trafficLayer.setMap(this.map);
			else
				this.trafficLayer.setMap(null);
		}
	}

/**********************/
/**********************/
/* Misc */
iFlightPlannerMap.prototype.cancelEvent =
	function(e)
	{
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
	}

iFlightPlannerMap.prototype.dataValid = 
	function(dataName,minutes)
	{
		var lastDate = this.dates[dataName];
		return (lastDate && lastDate > new Date(new Date() - minutes * 60000));
	}

iFlightPlannerMap.prototype.determineMarkerTitle =
	function(bestID,name,type,cls,latLng)
	{
		var title;
		
		if (cls != "L")
		{
			title = bestID;
			if (name && name.length) title += " - " + name;
			if (type && type.length) title += ": " + type;
		}
		else
			title = this.formatLatLngPairForDMST(latLng);
			
		return title;
	}
	
iFlightPlannerMap.prototype.determineMarkerTitleFromLocation =
	function(location)
	{
		return this.determineMarkerTitle(location.BestID,location.Name,location.Type,location.Class,new google.maps.LatLng(location.Lat,location.Lng));
	}
	
iFlightPlannerMap.prototype.escapeQuotes =
	function(value)
	{
		if (value && value.length)
			value = value.replace(/['']/g,"\\'").replace(/[""]/g,"&quot;");
		return value;
	}
	
iFlightPlannerMap.prototype.parseLocations = 
	function(data)
	{
		var locations = jsonParse(data);
		return locations;
	}
	
iFlightPlannerMap.prototype.setCenter =
	function(lat,lng)
	{
		ifpMap.map.setCenter(new google.maps.LatLng(lat,lng));
		modalDialog_toggle(ifpMap.id + "_LocationDialog",false);
	}

iFlightPlannerMap.prototype.setDimensions =
	function(w,h)
	{
		var map = ifpMap.map;
		var c = map.getCenter();
		//var b = map.getBounds();
		var d = map.getDiv();
		//d.style.width = w + "px";
		d.style.height = h + "px";
		google.maps.event.trigger(map, "resize");
		//map.panTo(c);
		//map.fitBounds(b);
	}

iFlightPlannerMap.prototype.setZoom =
	function(zoom)
	{
		ifpMap.map.setZoom(zoom);
		modalDialog_toggle(ifpMap.id + "_LocationDialog",false);
	}

/**********************/
/**********************/
/* Progress Window */
iFlightPlannerMap.prototype.toggleProgressIcon =
	function(show)
	{
		var el = document.getElementById("GoogleMapProgress");
		if (el)
			el.style.display = show ? "block" : "none";
	}

/**********************/
/**********************/
/* Radar/Satellite */
iFlightPlannerMap.prototype.getRadarOn = 
	function()
	{
		var on = false;
		if (this.radarSatelliteEnabled)
		{
			var field = document.getElementById(this.id + "_Radar");
			on = (field && field.checked);
		}
		return on;
	}
	
iFlightPlannerMap.prototype.getSatelliteOn = 
	function()
	{
		var on = false;
		if (this.radarSatelliteEnabled)
		{
			var field = document.getElementById(this.id + "_Satellite");
			on = (field && field.checked);
		}
		return on;
	}	

iFlightPlannerMap.prototype.toggleRadar = 
	function()
	{
		if (this.radar)
		{
			var on = this.getRadarOn();
			if (on)
				this.radar.show();
			else
				this.radar.hide();
		}
	}
	
iFlightPlannerMap.prototype.toggleSatellite = 
	function()
	{
		if (this.satellite)
		{
			var on = this.getSatelliteOn();
			if (on)
				this.satellite.show();
			else
				this.satellite.hide();
		}
	}
	
iFlightPlannerMap.prototype.toggleSatelliteType = 
	function()
	{
		if (this.satellite)
			this.satellite.draw();
	}	

/**********************/
/**********************/
/* Routes */
iFlightPlannerMap.prototype.addRouteMarker =
	function(insertAt,uniqueID,bestID,name,cls,type,usevalue,latLng,lat,lng,isFrom,isTo)
	{
		var location;
		var marker, previousMarker, nextMarker;
		var existingLine, routeLine, path;
		var index;
	
		//if not provided, create lat lng
		if (!latLng) latLng = new google.maps.LatLng(lat,lng);
		
		//create new location
		location = this.createLocationObject(uniqueID,bestID,bestID,name,cls,type,usevalue,latLng.lat(),latLng.lng())
	
		//create marker
		marker = this.createMarker(location,true,(cls == "A" ? this.markerType.ROUTE_AIRPORT : this.markerType.ROUTE_MARKER),100,false,isFrom,isTo);
		if (isFrom) marker.IsFrom = true;
		if (isTo) marker.IsTo = true;
		
		//determine how to add
		if (insertAt == 0) //add at beginning
		{
			//add to beginning of array & create new line (if necessary)
			this.routeMarkers.splice(0,0,marker);
			if (this.routeMarkers.length > 1)
			{
				nextMarker = this.routeMarkers[1];
				
				//create route line, add to array
				routeLine = this.createRouteLine(marker,nextMarker,0,false);
				this.routeLines.splice(0,0,routeLine);
				
				//set references
				marker.nextLine = routeLine;
				nextMarker.previousLine = routeLine;
			}			
		}
		else if (insertAt < 0 || insertAt >= this.routeMarkers.length) //add at end
		{
			//add to end of array & create new line (if necessary)
			this.routeMarkers[this.routeMarkers.length] = marker;
			if (this.routeMarkers.length > 1)
			{
				previousMarker = this.routeMarkers[this.routeMarkers.length - 2];
				
				//create route line
				routeLine = this.createRouteLine(previousMarker,marker,0,true);
				
				//set references
				previousMarker.nextLine = routeLine;
				marker.previousLine = routeLine;
			}
		}
		else if (insertAt < this.routeMarkers.length) //splice route line in two
		{
			//get current marker at index and insert new marker
			nextMarker = this.routeMarkers[insertAt];
			this.routeMarkers.splice(insertAt,0,marker);
			
			//get route line to splice
			existingLine = nextMarker.previousLine;
			
			//shorten current route line
			path = existingLine.getPath();
			path.setAt(1,latLng);
			existingLine.setPath(path);
			
			//create new route line
			routeLine = this.createRouteLine(marker,nextMarker,0,false);
			for (i=0; i<this.routeLines.length; i++)
				if (this.routeLines[i] == existingLine)
					index = i+1;
			this.routeLines.splice(index,0,routeLine);
			
			//correct line/marker references
			existingLine.endMarker = marker;
			marker.previousLine = existingLine;
			marker.nextLine = routeLine;
			nextMarker.previousLine = routeLine;
		}
		
		//rebuild route text, update distances, indicate route modified
		this.buildRouteText(this.standalone);
		this.updateRouteDistances();
		this.handleRouteModified(!isFrom && !isTo);
	}

iFlightPlannerMap.prototype.addRouteMarkerFromContextMenu =
	function()
	{
		var marker, value;
		
		//hide menu
		this.hideContextMenu();
		
		//get marker reference
		marker = this.contextMenuMarker;
		if (marker)
			this.addRouteMarker(-1,marker.UniqueID,marker.BestID,marker.Name,marker.Class,marker.Type,marker.Use,marker.getPosition(),0,0,false,false);
		else if (this.lastLatLng) //create marker from last lat lng
		{
			value = this.formatLatLngToUSDigitString(this.lastLatLng);
			this.addRouteMarker(-1,0,value,this.formatLatLngPairForDMST(this.lastLatLng," "),"L","Latitude/Longitude","",this.lastLatLng,0,0,false,false);
		}
	}
	
iFlightPlannerMap.prototype.addRouteMarkerFromDialog =
	function(uniqueID,bestID,name,cls,type,usevalue,lat,lng)
	{
		var insertAtStart = document.getElementById(this.id + "_Insert_Start");
		var insertAtFields = document.getElementById(this.id + "_Insert_At");
		var insertAtOptions = insertAtFields.getElementsByTagName("input");
		var insertAt;
		var insertAtSelected = false;
				
		//determine where to insert
		if (insertAtStart && insertAtStart.checked)
		{
			insertAt = 0;
			insertAtSelected = true;
		}
		else
		{
			for (var i=0; i<insertAtOptions.length; i++)
			{
				if (insertAtOptions[i].checked)
				{	
					insertAt = insertAtOptions[i].value;
					insertAtSelected = true;
				}
			}
		}
		
		//add marker, if spot selected
		if (insertAtSelected)
		{
			this.addRouteMarker(insertAt,uniqueID,bestID,name,cls,type,usevalue,null,lat,lng,false,false);
			modalDialog_toggle(this.id + "_LocationAddDialog",false);
		}
		else
			alert("Please indicate where in the route to insert the selected aviation location.");		
	}
	
iFlightPlannerMap.prototype.addRouteMarkerFromLineClick =
	function()
	{
		var index, bestID, latLng;
	
		//determine marker index to insert at
		for (var i=0; i<this.routeLines.length; i++)
			if (this.routeLines[i] == this.routeLineToSplice)
				index = i;
	
		//determine values
		latLng = this.lastLatLng;
		bestID = this.formatLatLngPairForDMST(latLng);
		
		//add marker
		this.addRouteMarker(index+1,0,bestID,bestID,"L","Latitude/Longitude","",latLng,0,0,false,false);
	}

iFlightPlannerMap.prototype.buildRouteText = 
	function(includeFromTo)
	{
		var text = "";
		var routeField = document.getElementById(this.routeFieldID);
		var routeParts = new Array();
		var marker, value;
		
		if (this.routeMarkers)
		{
			for (var i=0; i<this.routeMarkers.length; i++)
			{
				marker = this.routeMarkers[i];
				if (includeFromTo || (!marker.IsFrom && !marker.IsTo))
				{
					if (marker.Class == "A" || marker.Class == "C" || marker.Class == "F" || marker.Class == "V")
						value = marker.BestID;
					else
						value = this.formatLatLngToUSDigitString(marker.getPosition());
						
					routeParts[routeParts.length] = value;
				}
			}
		}
		text = routeParts.join(" ");
		
		if (routeField)
			routeField.value = text;
	}

iFlightPlannerMap.prototype.canCreateMarker =
	function(uniqueID,markerType,isFromServer)
	{
		var oldMarker;
		var canCreate = true;
		var isFavorite = false;
		var isCustomLocation = false;
		var markerToUse = null;
	
		//check if marker already exists
		if (uniqueID > 0)
		{
			oldMarker = this.markers[uniqueID];
			if (oldMarker)
			{
				if (oldMarker.MarkerType <= markerType)
				{
					//if request came from user interaction on map and marker is a favorite airport, then just upgrade the marker
					if (!isFromServer && oldMarker.MarkerType == this.markerType.FAVORITE_AIRPORT)
					{
						//upgrade existing marker to retain weather data
						oldMarker.MarkerType = markerType;
						oldMarker.IsFavorite = true;
						markerToUse = oldMarker;
						canCreate = false;
					}
					else if (!isFromServer && oldMarker.MarkerType == this.markerType.CUSTOM_LOCATION)
					{
						//upgrade existing marker
						oldMarker.MarkerType = markerType;
						markerToUse = oldMarker;
						canCreate = false;
					}
					else
					{
						//clear old marker from map
						oldMarker.setMap(null);
						google.maps.event.clearInstanceListeners(oldMarker);
						this.markers[uniqueID] = null;
						
						//determine if marker is a favorite
						if (oldMarker.MarkerType == this.markerType.FAVORITE_AIRPORT || oldMarker.IsFavorite)
							isFavorite = true;
					}
				}
				else
				{
					canCreate = false;
					markerToUse = oldMarker;
				}
			}
		}
	
		return {canCreate: canCreate, isFavorite: isFavorite, markerToUse: markerToUse};
	}
	
iFlightPlannerMap.prototype.clearRoute =
	function()
	{
		var marker;
	
		//clear previous routes
		for (var i=0; i<this.routeLines.length; i++)
		{
			this.routeLines[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.routeLines[i]);
		}
		this.routeLines.splice(0,this.routeLines.length);
		
		//clear previous route corridor markers
		for (var i=0; i<this.routeCorridorMarkers.length; i++)
		{
			this.routeCorridorMarkers[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.routeCorridorMarkers[i]);
			this.markers[this.routeCorridorMarkers[i].UniqueID] = null;
		}
		this.routeCorridorMarkers.splice(0,this.routeCorridorMarkers.length);
		
		//clear previous route markers
		for (var i=0; i<this.routeMarkers.length; i++)
		{
			marker = this.routeMarkers[i];
			if (marker.IsFavorite)
				marker.MarkerType = this.markerType.FAVORITE_AIRPORT;
			else if (marker.Class == "C")
				marker.MarkerType = this.markerType.CUSTOM_LOCATION;
			else
			{
				marker.setMap(null);
				marker.previousLine = null;
				marker.nextLine = null;
				google.maps.event.clearInstanceListeners(marker);
				this.markers[marker.UniqueID] = null;
			}
		}
		this.routeMarkers.splice(0,this.routeMarkers.length);
		
		//clear pending route markers
		for (var i=0; i<this.routeMarkersPending.length; i++)
		{
			this.routeMarkersPending[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.routeMarkersPending[i]);
		}
		this.routeMarkersPending.splice(0,this.routeMarkersPending.length);
	}

iFlightPlannerMap.prototype.createMarker =
	function(location,draggable,markerType,zIndex,isFromServer)
	{
		var marker, title, latLng, image, shadow;
		var result;

		//ensure marker can be created
		result = this.canCreateMarker(location.UniqueID,markerType,isFromServer);
		if (result.canCreate)
		{
			//create marker
			title = this.determineMarkerTitleFromLocation(location);
			latLng = new google.maps.LatLng(location.Lat,location.Lng);
			image = this.createMarkerImage(location.Class,location.FlightRules,location.Forecast);
			shadow = this.createMarkerShadowImage(location.Class);
			marker = new google.maps.Marker
						({
							title: title,
							position: latLng,
							icon: image,
							cursor: "pointer",
							shadow: shadow,
							draggable: draggable,
							raiseOnDrag: false,
							map: this.map,
							zIndex: zIndex,
							
							//custom properties
							UniqueID: location.UniqueID,
							BestID: location.BestID,
							ID: location.ID,
							Name: location.Name,
							Class: location.Class,
							Type: location.Type,
							Use: location.Use,
							MarkerType: markerType,
							Distance: location.Distance,
							IsFavorite: result.isFavorite
						});
				
			//add listeners
			if (!this.isApple)
				google.maps.event.addListener(marker, "click", function(event) {ifpMap.getLocationInfo(marker);});
			if (this.routesEnabled)
			{
				if (!this.isApple)
					google.maps.event.addListener(marker, "rightclick", function(event) {ifpMap.showContextMenu(event,marker,false);});
				else
					google.maps.event.addListener(marker, "click", function(event) {ifpMap.showContextMenu(event,marker,false);});
				google.maps.event.addListener(marker, "drag", function(event) {ifpMap.handleMarkerDrag(marker, event.latLng);});
				google.maps.event.addListener(marker, "dragend", function(event) {ifpMap.handleMarkerDragEnd(marker, event.latLng);});
			}
			
			//add marker to overall collection
			if (!isFromServer || markerType != this.markerType.CUSTOM_LOCATION)
				this.markers[marker.UniqueID] = marker;
		}
		else
			marker = result.markerToUse;
		
		return marker;
	}

iFlightPlannerMap.prototype.createMarkerImage =
	function(cls,flightRules,forecast)
	{
		var n, s, a;
		
		if (!flightRules) flightRules = "Unknown";
		if (!forecast) forecast = "Unknown";
		
		//determine marker image
		if (cls == "F")
		{
			n = "X";
			s = this.xvImageSize;
			a = this.xvImageAnchor;
		}	
		else if (cls == "V")
		{
			n = "V";
			s = this.xvImageSize;
			a = this.xvImageAnchor;
		}
		else if (cls == "L")
		{
			n = "LL";
			s = this.xvImageSize;
			a = this.xvImageAnchor;
		}
		else if (cls == "A")
		{
			n = "A_" + flightRules + "_" + forecast;
			s = this.aptImageSize;
			a = this.aptImageAnchor;	
		}
		else if (cls == "C")
		{
			n = "C";
			s = this.xvImageSize;
			a = this.xvImageAnchor;
		}
		
		return new google.maps.MarkerImage(this.markersBaseUrl + n + ".png",s,this.imageOrigin,a);
	}
	
iFlightPlannerMap.prototype.createMarkerShadowImage = 
	function(cls)
	{
		if (cls == "A")
			return new google.maps.MarkerImage(this.markersBaseUrl + "A_Shadow.png",this.shadowImageSize,this.imageOrigin,this.shadowImageAnchor);	
		else
			return null;
	}	

iFlightPlannerMap.prototype.createRouteCorridorLocationMarker =
	function(location)
	{
		var marker, result;
		
		//ensure marker can be created
		result = this.canCreateMarker(location.UniqueID,this.markerType.CORRIDOR_AIRPORT,true);
		if (result.canCreate)
		{
			var title = this.determineMarkerTitleFromLocation(location);
			var latLng = new google.maps.LatLng(location.Lat,location.Lng);
			var hasForecast = (location.Forecast != "Unknown");
			var anchor = (hasForecast ? this.squareImageAnchor : this.circleImageAnchor);
			var size = (hasForecast ? this.squareImageSize : this.circleImageSize);
			var image = new google.maps.MarkerImage(this.markersBaseUrl + location.FlightRules + (hasForecast ? "_" + location.Forecast : "Circle") + ".png",size,this.imageOrigin,anchor);
			marker = new google.maps.Marker
						({
							title: title,
							position: latLng,
							icon: image,
							map: this.map,
							zIndex: 89,
							
							//custom properties
							UniqueID: location.UniqueID,
							BestID: location.BestID,
							ID: location.ID,
							Name: location.Name,
							Class: location.Class,
							Type: location.Type,
							Use: location.Use,
							MarkerType: this.markerType.CORRIDOR_AIRPORT,
							IsFavorite: result.isFavorite
						});
			
			//add listener
			if (!this.isApple)
			{	
				google.maps.event.addListener(marker, "click", function(event) {ifpMap.getLocationInfo(marker);});
				google.maps.event.addListener(marker, "rightclick", function(event) {ifpMap.showContextMenu(event,marker,false);});
			}
			else
				google.maps.event.addListener(marker, "click", function(event) {ifpMap.showContextMenu(event,marker,false);});
			this.routeCorridorMarkers[this.routeCorridorMarkers.length] = marker;
			
			//set marker type, add to overall collection
			this.markers[location.UniqueID] = marker;
		}
		else
			marker = result.markerToUse;
		
		return marker;
	}

iFlightPlannerMap.prototype.createRouteLine =
	function(marker,endMarker,totalDistance,insertIntoCollection)
	{
		//create route, add to routes array
		var routeLine = new google.maps.Polyline
				({
					path: [marker.getPosition(),endMarker.getPosition()],
					strokeColor: "#000000", //"#0080C3",
					strokeOpacity: 0.6,
					strokeWeight: 8,
					map: this.map,
					geodesic: false,
					
					//custom properties
					startMarker: marker,
					endMarker: endMarker,
					distance: endMarker.Distance,
					totalDistance: totalDistance
				});
		
		//add listeners
		google.maps.event.addListener(routeLine, "click", function(event) {ifpMap.handleLineClick(event.latLng,routeLine);});
		if (this.routesEnabled)
		{
			google.maps.event.addListener(routeLine, "mousedown", function(event) {ifpMap.handleLineMouseDown(event.latLng,routeLine);});
			google.maps.event.addListener(routeLine, "mouseup", function(event) {ifpMap.handleLineMouseUp(routeLine);});
		}
		
		//add to collection, if requested
		if (insertIntoCollection)
			this.routeLines[this.routeLines.length] = routeLine;

		return routeLine;
	}

iFlightPlannerMap.prototype.createRoutePendingMarker =	
	function(uniqueid,id,name,type,lat,lng)
	{
		//create marker
		var title = this.determineMarkerTitleFromLocation(location);
		var latLng = new google.maps.LatLng(lat,lng);
		var image = new google.maps.MarkerImage(this.markersBaseUrl + "A_Pending.png",this.aptImageSize,this.imageOrigin,this.aptImageAnchor);
  		var shadow = this.createMarkerShadowImage("A");
  		var marker = new google.maps.Marker
					({
						title: title,
						position: latLng,
						icon: image,
						shadow: shadow,
						map: this.map,
						zIndex: 100,
						
						//custom properties
						UniqueID: uniqueid,
						BestID: id,
						ID: id,
						Name: name,
						Class: "P",
						Type: "Pending",
						Use: "",
						MarkerType: this.markerType.ROUTE_PENDING,
						Distance: 0
					});
		
		//add listener
		google.maps.event.addListener(marker, "click", function(event) {ifpMap.getLocationInfo(marker);});
		this.routeMarkersPending[this.routeMarkersPending.length] = marker;
	}

iFlightPlannerMap.prototype.drawRoute =
	function(locations,totalDistance)
	{
		var route;
		var location;
		var markerType, marker, lastMarker, line;
		var draggable;
		var fromValue, toValue;
		var isFrom, isTo;
		
		//get from and to values
		if (!this.standalone)
		{
			fromValue = document.getElementById(this.fromFieldID).value.toUpperCase();
			toValue = document.getElementById(this.toFieldID).value.toUpperCase();
		}
		
		//add markers, create coordinates
		for (var i=0; i<locations.length; i++)
		{
			location = locations[i];
		
			//set IsFrom/IsTo markers
			if (!this.standalone)
			{
				isFrom = (i == 0 && (location.BestID.toUpperCase() == fromValue || location.ID.toUpperCase() == fromValue));
				isTo = (i == (locations.length-1) && (location.BestID.toUpperCase() == toValue || location.ID.toUpperCase() == toValue));
			}
		
			//determine dragability and marker type
			draggable = (this.routesEnabled && (this.standalone || (!isFrom && !isTo)));
			markerType = (draggable ? this.markerType.ROUTE_MARKER : this.markerType.ROUTE_AIRPORT);
		
			//create marker, add to collection
			marker = this.createMarker(location,draggable,markerType,100,true);
			this.routeMarkers[this.routeMarkers.length] = marker;
			
			//set IsFrom/IsTo values
			if (isFrom) marker.IsFrom = true;
			if (isTo) marker.IsTo = true;
						
			//if not first location on route, create polyline from last marker to this marker
			if (i > 0 && i < locations.length)
			{
				line = this.createRouteLine(lastMarker,marker,totalDistance,true);	
				lastMarker.nextLine = line;
				marker.previousLine = line;				
			}
			
			//set last values
			lastMarker = marker;
		}
				
		//remove progress icon, route modified link		
		this.toggleProgressIcon(false);
		this.toggleRouteModified(false);
			
		//increment route count
		this.routeCount += 1;
	}
	
iFlightPlannerMap.prototype.drawRouteCorridorLocations =
	function(locations)
	{
		var location;
		var latLng;
	
		//add markers, create coordinates
		for (var i=0; i<locations.length; i++)
		{
			location = locations[i];
			latLng = new google.maps.LatLng(location.Lat,location.Lng);
		
			ifpMap.createRouteCorridorLocationMarker(location);
		}
	}

iFlightPlannerMap.prototype.fitToRoute =
	function()
	{
		var bounds;
		
		//get route bounds
		bounds = this.getRouteBounds();
		
		//if no bounds have been added, zoom to favorites & custom locations
		if (!bounds && (this.favoriteMarkers.length || this.customLocationMarkers.length))
		{
			bounds = new google.maps.LatLngBounds();
		
			//favorites
			for (var i=0; i<this.favoriteMarkers.length; i++)
				bounds.extend(this.favoriteMarkers[i].getPosition());
			
			//custom locations
			for (var i=0; i<this.customLocationMarkers.length; i++)
				bounds.extend(this.customLocationMarkers[i].getPosition());
		}
		
		//fit to bounds or zoom to entire US
		if (bounds)
			this.map.fitBounds(bounds);
		else
		{
			this.map.setCenter(new google.maps.LatLng(37.32, -94.37));
			this.map.setZoom(4);
		}
	}

iFlightPlannerMap.prototype.getRoute =
	function()
	{
		var routeText = this.getRouteText();
		if (routeText.length)
		{
			this.toggleProgressIcon(true);
			MapsService.GetRoute(routeText,MapsService_GetRoute_SucceededCallback);
		}
		else
		{
			this.clearRoute();
			this.toggleProgressIcon(false);
		}
	}

iFlightPlannerMap.prototype.getRouteBounds =
	function()
	{
		var bounds = new google.maps.LatLngBounds();
		var hasBounds = false;
		
		//extend bounds for route markers
		for (var i=0; i<this.routeMarkers.length; i++)
		{
			bounds.extend(this.routeMarkers[i].getPosition());
			hasBounds = true;
		}
		
		//if there is only 1 route marker, add route corridor markers to bounds
		if (this.routeMarkers.length < 2 && this.routeCorridorMarkers.length > 0)
		{
			for (var i=0; i<this.routeCorridorMarkers.length; i++)
			{	
				bounds.extend(this.routeCorridorMarkers[i].getPosition());
				hasBounds = true;
			}
		}
		
		if (!hasBounds) bounds = null;
			
		return bounds;
	}
	
iFlightPlannerMap.prototype.getRouteParts =
	function()
	{
		var routeText = this.getRouteText();
		var routeField = document.getElementById(this.routeFieldID);
		
		//clean up data
		routeText = routeText.replace(/^\s+|\s+$/g,"").replace(/[\s]+/g," ");
		
		//set field value with cleaned up value
		if (routeField)
			routeField.value = routeText;
		
		//split route text
		if (routeText && routeText.length)
			parts = routeText.split(" ");
		else
			parts = new Array();
			
		return parts;
	}	
	
iFlightPlannerMap.prototype.getRouteText =
	function()
	{
		var routeField = document.getElementById(this.routeFieldID);
		var value;
		if (routeField)
			value = routeField.value;
		else	
			value = "";
		return value;
	}

iFlightPlannerMap.prototype.handleLineClick =
	function(latLng,routeLine)
	{
		var value, isLL;
	
		//cancel timeout
		if (routeLine.timeout)
			window.clearTimeout(routeLine.timeout);
		
		//close previous info window
		if (this.infoWindow)
			this.infoWindow.close();
	
		var content = "<div class=\"GmInfo\">";
		content += "<div class=\"Title\">";
		if (routeLine.startMarker)
		{
			value = routeLine.startMarker.BestID;
			if (routeLine.startMarker.Class == "L") value = this.formatLatLngPairForDMST(this.convertUSDigitStringToLatLngPair(value));
			if (routeLine.startMarker.UniqueID > 0) 
				content += "<a href=\"javascript:ifpMap.getLocationInfoForUniqueID(" + routeLine.startMarker.UniqueID + ");\">" + value + "</a> to ";
			else
			{
				content += value + " to ";
				isLL = true;
			}
		}
		if (routeLine.endMarker)
		{
			value = routeLine.endMarker.BestID;
			if (routeLine.endMarker.Class == "L") value = this.formatLatLngPairForDMST(this.convertUSDigitStringToLatLngPair(value));
			if (routeLine.endMarker.UniqueID > 0) 
				content += "<a href=\"javascript:ifpMap.getLocationInfoForUniqueID(" + routeLine.endMarker.UniqueID + ");\">" + value + "</a>";
			else
				content += (isLL ? "<br />" : "") + value;
		}
		content += "</div>";
		content += "<div class=\"Label\">Segment</div><div class=\"Item\">" + routeLine.distance + " nm</div>";
		content += "<div class=\"Label\">Trip</div><div class=\"Item\">" + routeLine.totalDistance + " nm</div>";
		content += "</div>";

		this.infoWindow = new google.maps.InfoWindow({content: content, position: latLng});
		this.infoWindow.open(this.map);
	}	

iFlightPlannerMap.prototype.handleLineMouseDown = 
	function(latLng,routeLine)
	{
		//save lat lng and route line reference
		this.lastLatLng = latLng;
		this.routeLineToSplice = routeLine;
	
		routeLine.timeout = window.setTimeout("ifpMap.addRouteMarkerFromLineClick();", 200);
	}
	
iFlightPlannerMap.prototype.handleLineMouseUp = 
	function(routeLine)
	{
		if (routeLine.timeout)
			window.clearTimeout(routeLine.timeout);	
	}	

iFlightPlannerMap.prototype.handleMarkerDrag = 
	function(marker, latLng)
	{
		var path;
		
		//move polylines on either side to new latLng
		if (marker.previousLine)
		{
			path = marker.previousLine.getPath();
			path.setAt(1,latLng);
			marker.previousLine.setPath(path);
		}
		if (marker.nextLine)
		{
			path = marker.nextLine.getPath();
			path.setAt(0,latLng);
			marker.nextLine.setPath(path);
		}	
	}
	
iFlightPlannerMap.prototype.handleMarkerDragEnd = 
	function(marker, latLng)
	{
		var newMarker;
		var location;
		
		//turn marker into generic LL marker
		marker.UniqueID = 0;
		marker.BestID = this.formatLatLngToUSDigitString(latLng);
		marker.Name = this.formatLatLngPairForDMST(latLng);
		marker.Class = "L";
		
		//set marker title, icon, shadow
		marker.setTitle(this.determineMarkerTitle(marker.BestID,marker.Name,"",marker.Class,marker.getPosition()));
		marker.setIcon(this.createMarkerImage(marker.Class));
		marker.setShadow(null);
							
		//build route text, indicate route modified, update distances
		this.buildRouteText(this.standalone);
		this.handleRouteModified(true);
		this.updateRouteDistances();
				
		//finally, show context menu for further actions
		this.showContextMenu(null,marker,true);
	}	
	
iFlightPlannerMap.prototype.handleRouteModified = 
	function(hasCustomRouting)
	{
		//call handler for custom routing
		if (hasCustomRouting && this.routeCustomizeHandler)
			eval(this.routeCustomizeHandler + "('" + this.routeUpdatePrefix + "');");
	
		//show route modified
		this.toggleRouteModified(true);
		
		//hide nav log
		if (this.navLogViewID)
			navLogView_invalidate(this.navLogViewID);	
	}

iFlightPlannerMap.prototype.isLocationInRoute =
	function(id)
	{
		var routeParts = this.getRouteParts();
		var el;
		
		//upper case, then look for match
		id = id.toUpperCase();		
		for (var i=0; i<routeParts.length; i++)
		{
			if (routeParts[i].toUpperCase() == id)
				return true;		
		}
		
		/*
		//check from, to fields
		if (this.fromFieldID)
		{
			el = document.getElementById(this.fromFieldID);
			if (el && el.value.toUpperCase() == id)
				return true;
		}
		if (this.toFieldID)
		{
			el = document.getElementById(this.toFieldID);
			if (el && el.value.toUpperCase() == id)
				return true;
		}
		*/
		
		return false;
	}
	
iFlightPlannerMap.prototype.receiveRoute =
	function(result)
	{
		var locationSet;
	
		//clear previous route
		this.clearRoute();
	
		//parse new data
		locationSet = jsonParse(result);
		
		//draw route
		this.drawRouteCorridorLocations(locationSet.EnrouteLocations);
		this.drawRoute(locationSet.Locations,locationSet.TotalDistance);
		
		//determine how to set bounds
		//if this is first route generate, then always set bounds
		//if this is subsequent route generation, only set route if there is
		// no overlap in current view and route
		if (!this.routesEnabled || this.routeCount == 1)
			this.fitToRoute();
		else
		{
			var routeBounds = this.getRouteBounds();
			var viewBounds = this.map.getBounds();
			if (!routeBounds || !routeBounds.intersects(viewBounds))
				this.fitToRoute();
		}		
	}	
	
iFlightPlannerMap.prototype.removeRouteMarker =
	function(marker)
	{
		var previousMarker, nextMarker;
		var previousLine, nextLine;
		var path;
		var index = -1;
		var isFromToField = false;
		
		//if no marker provided, get from context
		if (!marker) marker = this.contextMenuMarker;
		if (marker)
		{
			//determine if this is from/to field
			isFromToField = (marker.IsFrom || marker.IsTo);
		
			//find index
			for (var i=0; i<this.routeMarkers.length; i++)
				if (marker == this.routeMarkers[i])
					index = i;
			
			//get previous/next marker references
			if (index > 0) previousMarker = this.routeMarkers[index-1];
			if (index < this.routeMarkers.length - 1) nextMarker = this.routeMarkers[index+1];
			
			//get previous/next line references			
			previousLine = marker.previousLine;
			nextLine = marker.nextLine;
			
			//clear From/To fields, if necessary
			if (!this.standalone)
			{
				if (marker.IsFrom)
				{
					document.getElementById(this.fromFieldID).value = "";
					marker.IsFrom = false;
				}
				if (marker.IsTo)
				{
					document.getElementById(this.toFieldID).value = "";
					marker.IsTo = false;
				}
			}
			
			//remove marker from array and map (or revert maker to Favorite/Custom Location)
			this.routeMarkers.splice(index,1);
			if (marker.IsFavorite)
			{
				marker.MarkerType = this.markerType.FAVORITE_AIRPORT;
				marker.IsFrom = false;
				marker.IsTo = false;
			}
			else if (marker.Class == "C")
				marker.MarkerType = this.markerType.CUSTOM_LOCATION;
			else
			{
				marker.setMap(null);
				if (marker.Class != "L") this.markers[marker.UniqueID] = null;
				marker.previousLine = null;
				marker.nextLine = null;
			}				
			
			//adjust lines
			if (previousLine)
			{
				if (nextMarker)
				{
					path = previousLine.getPath();
					path.setAt(1,nextMarker.getPosition());
					previousLine.setPath(path);
					nextMarker.previousLine = previousLine;
					previousLine.endMarker = nextMarker;
				}
				else
				{
					//find index
					for (var i=0; i<this.routeLines.length; i++)
						if (previousLine == this.routeLines[i])
							index = i;
							
					//remove line from map and array
					previousLine.setMap(null);
					this.routeLines.splice(index,1);
					
					//clear reference to line on previous marker
					if (previousMarker) previousMarker.nextLine = null;					
				}
			}
			
			//always remove next line, if present
			if (nextLine)
			{
				//find index
				for (var i=0; i<this.routeLines.length; i++)
					if (nextLine == this.routeLines[i])
						index = i;
				
				//remove line from map & array
				nextLine.setMap(null);
				this.routeLines.splice(index,1);
			}
				
			//rebuild route text, update route distances, indicate route modified
			this.buildRouteText(this.standalone);	
			this.updateRouteDistances();
			this.handleRouteModified(!isFromToField);
		}
	
		//close dialog/context menu
		modalDialog_toggle(this.id + "_LocationDialog",false);
		this.hideContextMenu();
	}
	
iFlightPlannerMap.prototype.redrawRoute = 
	function()
	{
		//show previous routes
		for (var i=0; i<this.routeLines.length; i++)
			this.routeLines[i].setMap(this.map);
		
		//show previous route corridor markers
		for (var i=0; i<this.routeCorridorMarkers.length; i++)
			this.routeCorridorMarkers[i].setMap(this.map);
		
		//show previous route markers
		for (var i=0; i<this.routeMarkers.length; i++)
			this.routeMarkers[i].setMap(this.map);
		
		//show pending route markers
		for (var i=0; i<this.routeMarkersPending.length; i++)
			this.routeMarkersPending[i].setMap(this.map);
	}
	
iFlightPlannerMap.prototype.setAirport = 
	function(type,airportCode)
	{
		var el;
		var fieldID;
		var fieldChanged = false;
		var marker;
		
		//get marker from context
		marker = this.contextMenuMarker;
		
		//if not standalone, use From, To and Alt fields
		if (!this.standalone)
		{
			//determine FieldID
			if (type == "F")
			{
				fieldID = this.fromFieldID;
				
				//remove existing From marker
				for (var i=0; i<this.routeMarkers.length; i++)
					if (this.routeMarkers[i].IsFrom)
						this.removeRouteMarker(this.routeMarkers[i]);	
						
				//add new From marker
				this.addRouteMarker(0,marker.UniqueID,marker.BestID,marker.Name,marker.Class,marker.Type,marker.Use,marker.getPosition(),0,0,true,false);
			}
			else if (type == "T")
			{
				fieldID = this.toFieldID;
				
				//remove existing To marker
				for (var i=0; i<this.routeMarkers.length; i++)
					if (this.routeMarkers[i].IsTo)
						this.removeRouteMarker(this.routeMarkers[i]);
						
				//add new To marker
				this.addRouteMarker(-1,marker.UniqueID,marker.BestID,marker.Name,marker.Class,marker.Type,marker.Use,marker.getPosition(),0,0,false,true);
			}
			else if (type == "A")
				fieldID = this.altFieldID;
			
			//set airport code	
			if (fieldID && fieldID.length)
			{
				el = document.getElementById(fieldID);
				if (el && el.value != airportCode)
				{
					el.value = airportCode;
					fieldChanged = true;
				}
			}
		}
		else
		{
			el = document.getElementById(this.routeFieldID);
			if (el)
			{
				if (type == "F")
					el.value = airportCode + " " + el.value;
				else if (type == "T")
					el.value = el.value + " " + airportCode;
					
				fieldChanged = true;
			}
		}
		
		//hide menu
		this.hideContextMenu();
		
		//route updated
		if (fieldChanged)
			this.handleRouteModified(false);
	}

iFlightPlannerMap.prototype.snapRouteMarkerTo =
	function(uniqueID,bestID,name,cls,type,usevalue,lat,lng)
	{
		var latLng;
		var marker = this.contextMenuMarker;
		
		//get lat lng
		latLng = new google.maps.LatLng(lat,lng);
	
		//update marker properties
		marker.setPosition(latLng);
		marker.UniqueID = uniqueID;
		marker.BestID = bestID;
		marker.Name = name;
		marker.Class = cls;
		marker.Type = type;
		marker.Use = usevalue;
		marker.setIcon(this.createMarkerImage(cls));
		marker.setShadow(this.createMarkerShadowImage(cls));
		marker.setTitle(this.determineMarkerTitle(bestID,name,type,cls,latLng));
		
		//rebuild route text, move route lines, update distances
		this.buildRouteText(this.standalone);
		this.handleMarkerDrag(marker,latLng);
		this.updateRouteDistances();
			
		//hide dialog, indicate route modified
		modalDialog_toggle(this.id + "_LocationAddDialog",false);
		this.handleRouteModified(true);
	}

iFlightPlannerMap.prototype.toggleRouteModified =
	function(show)
	{
		var el = document.getElementById(this.id + "_RouteModified");
		if (el) el.style.display = (show ? "block" : "none");
	}

iFlightPlannerMap.prototype.updateRoute = 
	function(reroute)
	{
		this.hideContextMenu();
	
		if (Page_ClientValidate(""))
		{
			if (reroute)
				this.toggleProgressIcon(true);
		
			if (!this.routeUpdateHandler)
			{
				if (reroute)
					this.getRoute();
			}
			else
				eval(this.routeUpdateHandler + "('" + this.routeUpdatePrefix + "'," + reroute + ");");
				
			//clear route modified
			this.toggleRouteModified(false);
		}
	}	
	
iFlightPlannerMap.prototype.updateRouteDistances =
	function()
	{
		var distance;
		var distances = new Array();
		var totalDistance = 0;	
		var i;	
	
		if (this.routeMarkers)
		{
			for (i=1; i<this.routeMarkers.length; i++)
			{
				//calculate distance
				distance = this.calculateLatLngDistance(this.routeMarkers[i-1].getPosition(),this.routeMarkers[i].getPosition(),false,true); 
				distances[distances.length] = distance.toFixed(2);
				
				//set marker distance, add to total
				this.routeMarkers[i].Distance = distance;
				totalDistance += distance;
			}
			totalDistance = totalDistance.toFixed(2);
		}
		
		if (this.routeLines)
		{
			for (i=0; i<this.routeLines.length; i++)
			{
				this.routeLines[i].distance = distances[i];
				this.routeLines[i].totalDistance = totalDistance;
			}
		}
	}

/**********************/
/**********************/
/* TFRs */
iFlightPlannerMap.prototype.clearTfrRegions =
	function()
	{
		//hide existing
		for (i=0; i<this.tfrRegions.length; i++)
		{
			this.tfrRegions[i].setMap(null);
			google.maps.event.clearInstanceListeners(this.tfrRegions[i]);
		}
		this.tfrRegions.splice(0,this.tfrRegions.length);
	}

iFlightPlannerMap.prototype.createTfrRegion =
	function(tfr)
	{
		//create region as polyline
		var region = new google.maps.Polyline
					({
						path: tfr.points,
						strokeColor: "#FF0000",
						strokeOpacity: 1,
						strokeWeight: 4,
						map: this.map
					});
		
		//add mouseover, mouseout listener
		google.maps.event.addListener(region, "click", function(event) {tfr.createInfoWindow(); ifpMap.showInfoWindow(event,tfr.infoWindow);});
		this.tfrRegions[this.tfrRegions.length] = region;
	}

iFlightPlannerMap.prototype.getTfrs =
	function()
	{
		if (this.dataValid("tfrs",20))
			this.showTfrs();
		else
			MapsService.GetTfrs(MapsService_GetTfrs_SucceededCallback);
	}	

iFlightPlannerMap.prototype.parseTfrs =
	function(data)
	{
		var tfrs = new Array();
		var tfr;
		var values;
		
		if (data && data.length)
		{
			values = data.split("$$");
			for (var i=0; i<values.length; i++)
			{
				var parts = values[i].split("|");
				tfr = new ifpTfr(parts[0],parts[1],parts[2],parts[3],parts[4],parts[5],parts[6]);
				var points = parts[7].split("ll:");
				for (var j=0; j<points.length; j++)
				{
					var partsLL = points[j].split(",");
					tfr.points[tfr.points.length] = new google.maps.LatLng(partsLL[0], partsLL[1]);
				}
				tfrs[tfrs.length] = tfr;
			}
		}
		
		return tfrs;
	}

iFlightPlannerMap.prototype.receiveTfrs =
	function(result)
	{
		this.tfrs = this.parseTfrs(result);
		this.dates["tfrs"] = new Date();
		this.showTfrs();
	}

iFlightPlannerMap.prototype.showTfrs =
	function()
	{
		var tfrs = this.tfrs;
		
		//clear existing
		this.clearTfrRegions();
		
		//create new regions
		for (var i=0; i<tfrs.length; i++)
			this.createTfrRegion(tfrs[i]);
	}

iFlightPlannerMap.prototype.toggleTfrs =
	function()
	{
		var field = document.getElementById(this.id + "_Tfrs");		
		if (field)
		{
			if (field.checked)
				this.getTfrs();
			else
				this.clearTfrRegions();
		}
		else //option not present, get TFRs always
			this.getTfrs();
	}
	
/**********************/
/**********************/
/* Unload */
iFlightPlannerMap.prototype.unload = 
	function()
	{
		//clear markers
		this.clearFavorites();
		this.clearCustomLocations();
		this.clearAdverseFlightRulesMarkers();
		this.clearAirSigmetRegions();
		this.clearRoute();
		this.clearTfrRegions();	
		
		//clear event listeners
		google.maps.event.clearInstanceListeners(this.map);
		
		//clear references
		this.controlsContainer = null;
		this.tacOption = null;
		this.trafficOption = null;
		
		//set reference to null
		ifpMap = null;
	}
	
/**********************/
/**********************/
/* Update */
iFlightPlannerMap.prototype.setUpdateTimer = 
	function()
	{
		this.updateTimer = window.setTimeout("ifpMap.updateFromTimer();", 600000);
	}

iFlightPlannerMap.prototype.updateFromTimer = 
	function()
	{
		this.toggleFavorites();
		this.toggleAdverseFlightRules();
		this.showAirSigmets();
		this.toggleTfrs();
		this.updateRoute();
		this.toggleSfc(false);
		this.toggleFreezing(false);
		if (this.radar) this.radar.draw();
		if (this.satellite) this.satellite.draw();
		
		//reset timer
		this.setUpdateTimer();
	}


/**********************/
/**********************/
/* WebService Callbacks */
function iFlightPlannerServices_SaveCustomLocation_SucceededCallback(result)
{
	ifpMap.receiveSaveCustomLocation(result);
}

function MapsService_GetAdjacentLocations_SucceededCallback(result)
{
	ifpMap.receiveAdjacentLocations(result);
}

function MapsService_GetAdverseFlightRulesLocations_SucceededCallback(result)
{
	ifpMap.receiveAdverseFlightRules(result);
}

function MapsService_GetAirSigmets_SucceededCallback(result)
{
	ifpMap.receiveAirSigmets(result);
}

function MapsService_GetCustomLocations_SucceededCallback(result)
{
	ifpMap.receiveCustomLocations(result);
}

function MapsService_GetFavorites_SucceededCallback(result)
{
	ifpMap.receiveFavorites(result);
}

function MapsService_GetLocationInfo_SucceededCallback(result)
{
	ifpMap.receiveLocationInfo(result);
}

function MapsService_GetRoute_SucceededCallback(result)
{
	ifpMap.receiveRoute(result);
}

function MapsService_GetTfrs_SucceededCallback(result)
{
	ifpMap.receiveTfrs(result);
}
