/* ----------------------
Satellite dish azimuth functions
Edison Dairo Jimenez 
10 Ag 2009
-----------------------*/
var RAD = Math.PI / 180;
var DEG = 180 / Math.PI;
var theWmm = new wmm();

var map;
var geocoder;
var address;
var obstacle = 0;
var marker2;
var line2;
var polyOptions;
var theElevation;
var theDistance;
var northAzimuth;
var magAzimuth;
var polarizationTilt;
var overlays = false;
var theAddress;
var myCountry = "";
var cacheCountry = "";
var markerCache;
var polyline;
var baseSiteUrl='http://www.satpointer.com';

function createCode() {
	form = document.getElementById("formWidget");
	var a;
	if (parseFloat(form.widgetWidth.value < 204)) theHeight = parseFloat(form.widgetHeight.value) + 220;
	else theHeight = parseFloat(form.widgetHeight.value) + 170;

	var code = '<iframe name="SatPointerWidget" frameborder="0" width="' + form.widgetWidth.value + '" height="' + theHeight + '" scrolling="no" src="' + baseSiteUrl + '/widgetApp.php?showMap=' + form.showMap.checked + '&showSatList=' + form.showSatList.checked + '&showResults=' + form.showResults.checked + '&bgColor=' + form.bgColor.value + '&widgetWidth=' + form.widgetWidth.value + '&widgetHeight=' + form.widgetHeight.value + '&rTextSize=' + form.rTextSize.value + '&rTextColor=' + form.rTextColor.value + '&mTextSize=' + form.mTextSize.value + '&mTextColor=' + form.mTextColor.value + '&satellite=' + form.satellite.options[form.satellite.selectedIndex].id + '"></iframe>';

	form.generatedCode.value = code;
	document.getElementById("divPreview").innerHTML = code;
}

function sendData() {
	form = document.getElementById('formSat');
	form.action = "sats.php";
	form.submit();
}

//control personalizado para mostrar/ocultar obstaculos
function visionControl() {}
visionControl.prototype = new GControl();
visionControl.prototype.initialize = function (map) {
	var container = document.createElement("div");
	var theButtonDiv = document.createElement("div");
	this.setButtonStyle_(theButtonDiv);
	container.appendChild(theButtonDiv);
	theButtonDiv.appendChild(document.createTextNode("Show Obstructions"));
	GEvent.addDomListener(theButtonDiv, "click", function () {
		if (obstacle == 1) obstacle = 0;
		else obstacle = 1;
		paintObstacle();
	});
	map.getContainer().appendChild(container);
	return container;
}

visionControl.prototype.getDefaultPosition = function () {
	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 30));
}

visionControl.prototype.setButtonStyle_ = function (button) {
	button.style.textDecoration = "none";
	button.style.color = "#000000";
	button.style.backgroundColor = "#BFDFFF";
	button.style.font = "11px Verdana";
	button.style.border = "1px solid black";
	button.style.padding = "2px";
	button.style.marginBottom = "3px";
	button.style.textAlign = "center";
	button.style.width = "7em";
	button.style.cursor = "pointer";
}

//google maps initialize
function initialize2(lat, lng) {
	if (GBrowserIsCompatible()) {
		geocoder = new GClientGeocoder();
		form = document.getElementById('formSat');
		if (form.satellite.value == "Nan")
			alert("Please select a satellite.");
		else
		{
			geocoder.getLocations([lat, lng], updateAddress2);
		}
	}
}

function initialize() {
	if (GBrowserIsCompatible()) {
		myIcon = new GIcon(G_DEFAULT_ICON);
		myIcon.image = "skin/pointMarker.png";
		myIcon.shadow = "skin/pointMarkerShadow.png";
		myIcon.iconSize = new GSize(29, 38);
		myIcon.iconAnchor = new GPoint(13, 37);
		myIcon.shadowSize = new GSize(39, 34);

		map = new GMap2(document.getElementById("theMapCanvas"));
		map.enableScrollWheelZoom();
		var center = new GLatLng(37.4419, -122.1419);
		map.setCenter(center, 16);
		var mapControl = new GMapTypeControl();
		map.addControl(mapControl);
		map.addControl(new GLargeMapControl());
		map.addControl(new visionControl());
		geocoder = new GClientGeocoder();
		showLocation();
	}
}

//busqueda

function showLocation() {
	form = document.getElementById('formSat');
	if (form.satellite.value != "Nan") {
		form = document.getElementById('formSearch');
		geocoder.getLocations(form.address.value, addAddressToMap);
	}
	else {
		alert("Please select a satellite.");
		form.satellite.focus();
	}
}

//muestra el resultado dela busqueda en el mapa
function addAddressToMap(response) {
	if (!response || response.Status.code != 200) {
		alert("Sorry, unable to locate that address, Please try again");
	} else {
		form = document.getElementById('formSat');

		place = response.Placemark[0];
		myCountry = place.AddressDetails.Country.CountryNameCode;
		point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
		theAddress = place.address;
		marker = new GMarker(point, {
			draggable: true,
			icon: myIcon,
			bouncy: false
		});

		GEvent.addListener(marker, "dragstart", function () {
				map.closeInfoWindow();
		});
		GEvent.addListener(marker, "click", function () {
			obstacle = 0;
			overlays = false;
			markerPointText();
		});
		GEvent.addListener(marker, "dragend", dragMarker);

		form.latitud.value = place.Point.coordinates[1];
		form.longitud.value = place.Point.coordinates[0];
		calculate();
	}
}

function dragMarker() {
	form = document.getElementById('formSat');
	if (form.satellite.value == "Nan")
		form.satellite.selectedIndex+=1;

	form.latitud.value = marker.getLatLng().lat();
	form.longitud.value = marker.getLatLng().lng();
	geocoder.getLocations(marker.getLatLng(), updateAddress);
}

//round
function round(number, digits) {
	ten = 1;
	for (i = 0; i < digits; i++)
	ten = ten * 10;
	return Math.round(number * ten) / ten;
}

//calculo
function calculate() {
	form = document.getElementById('formSat');
	theSatelliteName = form.satellite.options[form.satellite.selectedIndex].text;

	satellite = form.satellite.value;
	latitud = form.latitud.value;
	longitud = form.longitud.value;

	espacioLongitud = (longitud - satellite) / DEG;
	latitudRadianes = latitud / DEG;
	r1 = 1 + 35786 / 6378.16;
	v1 = r1 * Math.cos(latitudRadianes) * Math.cos(espacioLongitud) - 1;
	v2 = r1 * Math.sqrt(1 - Math.cos(latitudRadianes) * Math.cos(latitudRadianes) * Math.cos(espacioLongitud) * Math.cos(espacioLongitud));
	theElevation = DEG * Math.atan((v1 / v2));
	northAzimuth = 180 + DEG * Math.atan(Math.tan(espacioLongitud) / Math.sin(latitudRadianes));
	if (latitud < 0) northAzimuth = northAzimuth - 180.0;
	if (northAzimuth < 0) northAzimuth = northAzimuth + 360.0;

	polarizationTilt = -DEG * Math.atan(Math.sin((satellite - longitud) / DEG) / Math.tan(latitudRadianes));

	p1 = 35786 * 35786;
	p2 = 2 * 6378.16 * (35786 + 6378.16);
	p3 = 1 - Math.cos(latitud / DEG) * Math.cos(-espacioLongitud);
	theDistance = Math.sqrt(p1 + p2 * p3);

	mlatitud = latitud;
	if (latitud < 0) mlatitud = -latitud;
	if (mlatitud > 80.0) {
		polarOffsetTilt = "lat";
		polarAxis = "lat";
	}
	else {
		polarOffsetTilt = 7.6 - (mlatitud - 96.8) * (mlatitud - 96.8) / (35.0 * 35.0) + mlatitud / 69.0 - 0.1 * Math.sin(mlatitud / 12.0);
		polarAxis = mlatitud - (mlatitud - 43.0) * (mlatitud - 43.0) / (51.3 * 51.3) + 0.000556 * mlatitud + 0.68;
	}

	magAzimuth = theWmm.declination(latitud, longitud);
	if (Math.abs(magAzimuth) > 1000) {
		//alert("Magnetic model not accurate");
		magAzimuth = magAzimuth / 10000;
	}
	markerPointText();

}

function updateAddress2(response) {
	if (!response || response.Status.code != 200) {
		alert("Sorry, unable to locate that address, Please try again");
	} else {
		place = response.Placemark[0];
		point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
		theAddress = place.address;
		myCountry = place.AddressDetails.Country.CountryNameCode;
		form = document.getElementById('theAddress');
		form.innerHTML = theAddress;
	}
}

function updateAddress(response) {
	if (!response || response.Status.code != 200) {
		alert("Sorry, unable to locate that address, Please try again");
	} else {
		place = response.Placemark[0];
		point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
		theAddress = place.address;
		myCountry = place.AddressDetails.Country.CountryNameCode;
		calculate();
	}
}

function markerPointText() {
	map.clearOverlays();
	map.addOverlay(marker);

	form = document.getElementById('formSat');
	theSatelliteName = form.satellite.options[form.satellite.selectedIndex].text;
	satellite = form.satellite.value;
	latitud = form.latitud.value;
	longitud = form.longitud.value;

	markerString = '<span class=\"gMapHelp\"><b>Address: <\/b>' + theAddress + '<br \/>' + '<b>Latitude: <\/b>' + round(latitud, 4) + '&deg;<br\/>' + '<b>Longitude: <\/b>' + round(longitud, 4) + '&deg;<br\/><br\/>' + '<b>Satellite: <\/b>' + form.satellite.options[form.satellite.selectedIndex].text + '<br\/>';

	document.getElementById('latitude').innerHTML = "Latitude: " + round(latitud, 4) + "&deg;";
	document.getElementById('longitude').innerHTML = "Longitude: " + round(longitud, 4) + "&deg;";
	document.getElementById('satelliteName').innerHTML = "Name: " + theSatelliteName;
	document.getElementById('distance').innerHTML = "Distance: " + round(theDistance, 2) + " Km";

	if (theElevation < 0) {
		document.getElementById('elevation').innerHTML = "<span class=\"red\">Dish | Elevation: " + round(theElevation, 1) + "&deg; Out of zone!</span>";
		markerString += "<span class=\"red\"><b class=\"red\">Dish | Elevation:<\/b> " + round(theElevation, 1) + "&deg; Out of zone!<\/span><br\/>";
		color = "#ff0000";
	}
	else {
		document.getElementById('elevation').innerHTML = "Dish | Elevation: " + round(theElevation, 1) + "&deg;";
		markerString += "<b>Dish | Elevation: <\/b>" + round(theElevation, 1) + "&deg;<br\/>";
		color = "#aad321";
	}
		if (polarizationTilt < 0) document.getElementById('skew').innerHTML = "Skew of LNB: " + round(polarizationTilt, 1) + "&deg; <img src=\"skin/arrowl.png\" alt\"\"/>";
	else document.getElementById('skew').innerHTML = "Skew of LNB: " + round(polarizationTilt, 1) + "&deg; <img src=\"skin/arrowr.png\" alt\"\"/>";
	marker.openInfoWindowHtml(markerString);
	document.getElementById('azimuthTrue').innerHTML = "Azimuth | True: " + round(northAzimuth, 2) + "&deg;";
	markerString += "<b>Azimuth (True): <\/b>" + round(northAzimuth, 2) + "&deg;<br\/>";
	document.getElementById('azimuthMagn').innerHTML = "Azimuth | Magnetic: " + round((northAzimuth - magAzimuth), 2) + "&deg;";
	markerString += "<b>Azimuth (Magn.): <\/b>" + round((northAzimuth - magAzimuth), 2) + "&deg;<br\/></span>";


	
	paintLine2(satellite,color);
	showMultiLines();
	paintObstacle();
	mostPopular();
	
}

function paintLine2(satLng,color)
{
	var e = marker.getPoint();
	var g = new GLatLng(0,satLng );
	var pointsArray = new Array();
	var i = e.y * RAD;
	var j = e.x * RAD;
	var k = g.y * RAD;
	var l = g.x * RAD;
	var m = (l - j);
	var o = (m * 180 / Math.PI).fixLng();
	m = o * RAD;
	var f = (m / 50);
	var q = j;
	var r = i;
	pointsArray.push(new GLatLng(r / RAD, q / RAD));
	for (var n = 0; n < 50; n++) {
		r = r + f * Math.cos(r) / Math.tan(Math.PI - Math.atan(Math.tan(l - q) / Math.sin(r)));
		q = q + f;
		pointsArray.push(new GLatLng(r / RAD, q / RAD));
	}
	pointsArray.push(new GLatLng(0, l / RAD));
	polyline = new GPolyline(pointsArray, color, 3, 0.8);
	map.addOverlay(polyline);
}

//encargado de pintar y calcular el obstaculo
function paintObstacle() {
	if (obstacle == 0) {
		if (overlays == true) {
			map.removeOverlay(marker2);
			map.removeOverlay(line2);
		}
		overlays = false;
	}
	else {
		form = document.getElementById('formSat');
		posAntena = marker.getLatLng();
		posSatellite = form.satellite.value;
		if (overlays == false) {
			marker2 = new GMarker(new GLatLng(marker.getLatLng().lat() - 0.004, marker.getLatLng().lng()), {
				draggable: true,
				bouncy: false
			});
			map.addOverlay(marker2);
			marker2.setPoint(polyline.GetPointAtDistance(50.0));
		}
		else {
			map.addOverlay(marker2);
		}

		GEvent.addListener(marker2, "dragstart", function () {
			map.removeOverlay(line2);
		});
		GEvent.addListener(marker2, "dragend", obstacleMarkerLine);
		GEvent.addListener(marker2, "click", obstacleMarkerText);
		obstacleMarkerLine();
	}
}

GPolygon.prototype.GetPointAtDistance = function (metres) {
	if (metres == 0) return this.getVertex(0);
	if (metres < 0) return null;
	var dist = 0;
	var olddist = 0;
	for (var i = 1;
	(i < this.getVertexCount() && dist < metres); i++) {
		olddist = dist;
		dist += this.getVertex(i).distanceFrom(this.getVertex(i - 1));
	}
	if (dist < metres) {
		return null;
	}
	var p1 = this.getVertex(i - 2);
	var p2 = this.getVertex(i - 1);
	var m = (metres - olddist) / (dist - olddist);
	return new GLatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
}

GPolyline.prototype.GetPointAtDistance = GPolygon.prototype.GetPointAtDistance;

function obstacleMarkerLine() {
	line2 = new GPolyline([
	new GLatLng(marker.getLatLng().lat(), marker.getLatLng().lng()), new GLatLng(marker2.getLatLng().lat(), marker2.getLatLng().lng())], "#111111", 3, 1, polyOptions);
	map.addOverlay(line2);
	obstacleMarkerText();
}

function obstacleMarkerText() {
	mydistance = marker2.getLatLng().distanceFrom(marker.getLatLng());
	text = "<span class=\"gMapHelp\">Move the RED marker to calculate <br\/>maximum height allowed for Obstructions.<br\/><br\/><b>Distance: <\/b>" + round(mydistance) + " m<br\/><b>Max Height: <\/b>";
	if (theElevation < 0) text += "0 m";
	else {
		text += round(Math.tan(RAD * theElevation) * mydistance, 2) + " m";
	}
	text += "</span><br\/>&nbsp;";
	marker2.openInfoWindow(text);
	overlays = true;
}

Number.prototype.fixLng = function () {
	lon = this;
	while (lon < -180) {
		lon += 360;
	}
	while (lon > 180) {
		lon -= 360;
	}
	return lon;
}

function setSatellite(id) {
	form = document.getElementById(id);
	form.selected = true;
	dragMarker();
}
/*---------------------
ajax call
---------------------*/
function mostPopular() {
	form = document.getElementById('formSat');
	if (myCountry != "" && myCountry != cacheCountry) {
		GDownloadUrl(baseSiteUrl+"/satpage.php?action=ajaxCall&country=" + myCountry + "&satellite=" + form.satellite.options[form.satellite.selectedIndex].id, function (data, data2) {
			document.getElementById("mostPopular").innerHTML = data;
		});
		cacheCountry = myCountry;
	}
	else satUpdate();
	//alert("ajaxCall: "+baseSiteUrl+"/satpage.php?action=ajaxCall&country=" + myCountry + "&satellite=" + form.satellite.options[form.satellite.selectedIndex].id);
}
function satUpdate() {
	form = document.getElementById('formSat');
	
	GDownloadUrl(baseSiteUrl+"/satpage.php?action=ajaxSatUpdate&country=" + myCountry + "&satellite=" + form.satellite.options[form.satellite.selectedIndex].id);
	//alert("ajaxSat: "+baseSiteUrl+"/satpage.php?action=ajaxSatUpdate&country=" + myCountry + "&satellite=" + form.satellite.options[form.satellite.selectedIndex].id);
}

/* ----------------------
Grid & Magnetic Variance Scripts 
Bill Chadwick (w.chadwick@sky.com) 10/2008
adapted Charles Harrison (jj@javajive.macfh.co.uk) 2009
-----------------------*/
function wmm() {
	this.beg = 2005;
	this.coff = [
		[1, 0, -29556.8, 0.0, 8.0, 0.0],[1, 1, -1671.7, 5079.8, 10.6, -20.9],
		[2, 0, -2340.6, 0.0, -15.1, 0.0],[2, 1, 3046.9, -2594.7, -7.8, -23.2],
		[2, 2, 1657.0, -516.7, -0.8, -14.6],[3, 0, 1335.4, 0.0, 0.4, 0.0],
		[3, 1, -2305.1, -199.9, -2.6, 5.0],[3, 2, 1246.7, 269.3, -1.2, -7.0],
		[3, 3, 674.0, -524.2, -6.5, -0.6],[4, 0, 919.8, 0.0, -2.5, 0.0],
		[4, 1, 798.1, 281.5, 2.8, 2.2],[4, 2, 211.3, -226.0, -7.0, 1.6],
		[4, 3, -379.4, 145.8, 6.2, 5.8],[4, 4, 100.0, -304.7, -3.8, 0.1],
		[5, 0, -227.4, 0.0, -2.8, 0.0],[5, 1, 354.6, 42.4, 0.7, 0.0],
		[5, 2, 208.7, 179.8, -3.2, 1.7],[5, 3, -136.5, -123.0, -1.1, 2.1],
		[5, 4, -168.3, -19.5, 0.1, 4.8],[5, 5, -14.1, 103.6, -0.8, -1.1],
		[6, 0, 73.2, 0.0, -0.7, 0.0],[6, 1, 69.7, -20.3, 0.4, -0.6],
		[6, 2, 76.7, 54.7, -0.3, -1.9],[6, 3, -151.2, 63.6, 2.3, -0.4],
		[6, 4, -14.9, -63.4, -2.1, -0.5],[6, 5, 14.6, -0.1, -0.6, -0.3],
		[6, 6, -86.3, 50.4, 1.4, 0.7],[7, 0, 80.1, 0.0, 0.2, 0.0],
		[7, 1, -74.5, -61.5, -0.1, 0.6],[7, 2, -1.4, -22.4, -0.3, 0.4],
		[7, 3, 38.5, 7.2, 1.1, 0.2],[7, 4, 12.4, 25.4, 0.6, 0.3],
		[7, 5, 9.5, 11.0, 0.5, -0.8],[7, 6, 5.7, -26.4, -0.4, -0.2],
		[7, 7, 1.8, -5.1, 0.6, 0.1],[8, 0, 24.9, 0.0, 0.1, 0.0],
		[8, 1, 7.7, 11.2, 0.3, -0.2],[8, 2, -11.6, -21.0, -0.4, 0.1],
		[8, 3, -6.9, 9.6, 0.3, 0.3],[8, 4, -18.2, -19.8, -0.3, 0.4],
		[8, 5, 10.0, 16.1, 0.2, 0.1],[8, 6, 9.2, 7.7, 0.4, -0.2],
		[8, 7, -11.6, -12.9, -0.7, 0.4],[8, 8, -5.2, -0.2, 0.4, 0.4],
		[9, 0, 5.6, 0.0, 0.0, 0.0],[9, 1, 9.9, -20.1, 0.0, 0.0],
		[9, 2, 3.5, 12.9, 0.0, 0.0],[9, 3, -7.0, 12.6, 0.0, 0.0],
		[9, 4, 5.1, -6.7, 0.0, 0.0],[9, 5, -10.8, -8.1, 0.0, 0.0],
		[9, 6, -1.3, 8.0, 0.0, 0.0],[9, 7, 8.8, 2.9, 0.0, 0.0],
		[9, 8, -6.7, -7.9, 0.0, 0.0],[9, 9, -9.1, 6.0, 0.0, 0.0],
		[10, 0, -2.3, 0.0, 0.0, 0.0],[10, 1, -6.3, 2.4, 0.0, 0.0],
		[10, 2, 1.6, 0.2, 0.0, 0.0],[10, 3, -2.6, 4.4, 0.0, 0.0],
		[10, 4, 0.0, 4.8, 0.0, 0.0],[10, 5, 3.1, -6.5, 0.0, 0.0],
		[10, 6, 0.4, -1.1, 0.0, 0.0],[10, 7, 2.1, -3.4, 0.0, 0.0],
		[10, 8, 3.9, -0.8, 0.0, 0.0],[10, 9, -0.1, -2.3, 0.0, 0.0],
		[10, 10, -2.3, -7.9, 0.0, 0.0],[11, 0, 2.8, 0.0, 0.0, 0.0],
		[11, 1, -1.6, 0.3, 0.0, 0.0],[11, 2, -1.7, 1.2, 0.0, 0.0],
		[11, 3, 1.7, -0.8, 0.0, 0.0],[11, 4, -0.1, -2.5, 0.0, 0.0],
		[11, 5, 0.1, 0.9, 0.0, 0.0],[11, 6, -0.7, -0.6, 0.0, 0.0],
		[11, 7, 0.7, -2.7, 0.0, 0.0],[11, 8, 1.8, -0.9, 0.0, 0.0],
		[11, 9, 0.0, -1.3, 0.0, 0.0],[11, 10, 1.1, -2.0, 0.0, 0.0],
		[11, 11, 4.1, -1.2, 0.0, 0.0],[12, 0, -2.4, 0.0, 0.0, 0.0],
		[12, 1, -0.4, -0.4, 0.0, 0.0],[12, 2, 0.2, 0.3, 0.0, 0.0],
		[12, 3, 0.8, 2.4, 0.0, 0.0],[12, 4, -0.3, -2.6, 0.0, 0.0],
		[12, 5, 1.1, 0.6, 0.0, 0.0],[12, 6, -0.5, 0.3, 0.0, 0.0],
		[12, 7, 0.4, 0.0, 0.0, 0.0],[12, 8, -0.3, 0.0, 0.0, 0.0],
		[12, 9, -0.3, 0.3, 0.0, 0.0],[12, 10, -0.1, -0.9, 0.0, 0.0],
		[12, 11, -0.3, -0.4, 0.0, 0.0],[12, 12, -0.1, 0.8, 0.0, 0.0]];
	this.c = new Array(13);
	this.cd = new Array(13);
	this.tc = new Array(13);
	this.dp = new Array(13);
	this.k = new Array(13);
	for (var i = 0; i < 13; i++) {
		this.c[i] = new Array(13);
		this.cd[i] = new Array(13);
		this.tc[i] = new Array(13);
		this.dp[i] = new Array(13);
		this.k[i] = new Array(13);
	}
	this.snorm = new Array(169);
	this.sp = new Array(13);
	this.cp = new Array(13);
	this.fn = new Array(13);
	this.fm = new Array(13);
	this.pp = new Array(13);
	var maxord = 12;
	var i, j, D1, D2, n, m;
	var gnm, hnm, dgnm, dhnm, flnmj;
	var c_str;
	var c_flds;
	maxord = 12;
	this.sp[0] = 0.0;
	this.cp[0] = this.snorm[0] = this.pp[0] = 1.0;
	this.dp[0][0] = 0.0;
	this.c[0][0] = 0;
	this.cd[0][0] = 0;
	for (i = 0; i < this.coff.length; i++) {
		var c_flds = this.coff[i];
		n = c_flds[0];
		m = c_flds[1];
		gnm = c_flds[2];
		hnm = c_flds[3];
		dgnm = c_flds[4];
		dhnm = c_flds[5];
		if (m <= n) {
			this.c[m][n] = gnm;
			this.cd[m][n] = dgnm;
			if (m != 0) {
				this.c[n][m - 1] = hnm;
				this.cd[n][m - 1] = dhnm;
			}
		}
	}
	this.snorm[0] = 1.0;
	for (n = 1; n <= maxord; n++) {
		this.snorm[n] = this.snorm[n - 1] * (2 * n - 1) / n;
		j = 2;
		for (m = 0, D1 = 1, D2 = (n - m + D1) / D1; D2 > 0; D2--, m += D1) {
			this.k[m][n] = (((n - 1) * (n - 1)) - (m * m)) / ((2 * n - 1) * (2 * n - 3));
			if (m > 0) {
				flnmj = ((n - m + 1) * j) / (n + m);
				this.snorm[n + m * 13] = this.snorm[n + (m - 1) * 13] * Math.sqrt(flnmj);
				j = 1;
				this.c[n][m - 1] = this.snorm[n + m * 13] * this.c[n][m - 1];
				this.cd[n][m - 1] = this.snorm[n + m * 13] * this.cd[n][m - 1];
			}
			this.c[m][n] = this.snorm[n + m * 13] * this.c[m][n];
			this.cd[m][n] = this.snorm[n + m * 13] * this.cd[m][n];
		}
		this.fn[n] = (n + 1);
		this.fm[n] = n;
	}
	this.k[1][1] = 0;
	this.fm[0] = 0;
}

//calculate degree diference between magnetic and geographic north
wmm.prototype.declination = function (latitudeDegrees, longitudeDegrees, altitudeKm, yearFloat) {
	var a = 6378.137;
	var b = 6356.7523142;
	var re = 6371.2;
	var a2 = a * a;
	var b2 = b * b;
	var c2 = a2 - b2;
	var a4 = a2 * a2;
	var b4 = b2 * b2;
	var c4 = a4 - b4;
	var D3, D4;
	var dip, ti, gv, dec;
	var n, m;
	var pi, dt, rlon, rlat, srlon, srlat, crlon, crlat, srlat2, crlat2, q, q1, q2, ct, d, aor, ar, br, r2, bpp, par, temp1, parp, temp2, bx, by, bz, bh, bp, bt, st, ca, sa;
	var maxord = 12;
	var alt = altitudeKm ? altitudeKm : 0;
	var glon = longitudeDegrees;
	var glat = latitudeDegrees;
	if (yearFloat != undefined) dt = yearFloat;
	else {
		dt = new Date();
		dt = dt.getFullYear() + (dt - new Date(dt.getFullYear(), 0, 0, 0, 0, 0, 0)) / (365.25 * 24 * 60 * 60 * 1000);
	}
	dt -= this.beg;
	rlon = glon * RAD;
	rlat = glat * RAD;
	srlon = Math.sin(rlon);
	srlat = Math.sin(rlat);
	crlon = Math.cos(rlon);
	crlat = Math.cos(rlat);
	srlat2 = srlat * srlat;
	crlat2 = crlat * crlat;
	this.sp[1] = srlon;
	this.cp[1] = crlon;
	q = Math.sqrt(a2 - c2 * srlat2);
	q1 = alt * q;
	q2 = ((q1 + a2) / (q1 + b2)) * ((q1 + a2) / (q1 + b2));
	ct = srlat / Math.sqrt(q2 * crlat2 + srlat2);
	st = Math.sqrt(1.0 - (ct * ct));
	r2 = (alt * alt) + 2.0 * q1 + (a4 - c4 * srlat2) / (q * q);
	r = Math.sqrt(r2);
	d = Math.sqrt(a2 * crlat2 + b2 * srlat2);
	ca = (alt + d) / r;
	sa = c2 * crlat * srlat / (r * d);
	for (m = 2; m <= maxord; m++) {
		this.sp[m] = this.sp[1] * this.cp[m - 1] + this.cp[1] * this.sp[m - 1];
		this.cp[m] = this.cp[1] * this.cp[m - 1] - this.sp[1] * this.sp[m - 1];
	}
	aor = re / r;
	ar = aor * aor;
	br = bt = bp = bpp = 0.0;
	for (n = 1; n <= maxord; n++) {
		ar = ar * aor;
		for (m = 0, D3 = 1, D4 = (n + m + D3) / D3; D4 > 0; D4--, m += D3) {
			if (n == m) {
				this.snorm[n + m * 13] = st * this.snorm[n - 1 + (m - 1) * 13];
				this.dp[m][n] = st * this.dp[m - 1][n - 1] + ct * this.snorm[n - 1 + (m - 1) * 13];
			}
			else if (n == 1 && m == 0) {
				this.snorm[n + m * 13] = ct * this.snorm[n - 1 + m * 13];
				this.dp[m][n] = ct * this.dp[m][n - 1] - st * this.snorm[n - 1 + m * 13];
			}
			else if (n > 1 && n != m) {
				if (m > n - 2) this.snorm[n - 2 + m * 13] = 0.0;
				if (m > n - 2) this.dp[m][n - 2] = 0.0;
				this.snorm[n + m * 13] = ct * this.snorm[n - 1 + m * 13] - this.k[m][n] * this.snorm[n - 2 + m * 13];
				this.dp[m][n] = ct * this.dp[m][n - 1] - st * this.snorm[n - 1 + m * 13] - this.k[m][n] * this.dp[m][n - 2];
			}
			this.tc[m][n] = this.c[m][n] + dt * this.cd[m][n];
			if (m != 0) this.tc[n][m - 1] = this.c[n][m - 1] + dt * this.cd[n][m - 1];
			par = ar * this.snorm[n + m * 13];
			if (m == 0) {
				temp1 = this.tc[m][n] * this.cp[m];
				temp2 = this.tc[m][n] * this.sp[m];
			}
			else {
				temp1 = this.tc[m][n] * this.cp[m] + this.tc[n][m - 1] * this.sp[m];
				temp2 = this.tc[m][n] * this.sp[m] - this.tc[n][m - 1] * this.cp[m];
			}
			bt = bt - ar * temp1 * this.dp[m][n];
			bp += (this.fm[m] * temp2 * par);
			br += (this.fn[n] * temp1 * par);
			if (st == 0.0 && m == 1) {
				if (n == 1) this.pp[n] = this.pp[n - 1];
				else this.pp[n] = this.ct * this.pp[n - 1] - this.k[m][n] * this.pp[n - 2];
				parp = ar * this.pp[n];
				bpp += (this.fm[m] * temp2 * parp);
			}
		}
	}
	if (st == 0.0) bp = bpp;
	else bp /= st;
	bx = -bt * ca - br * sa;
	by = bp;
	bz = bt * sa - br * ca;
	bh = Math.sqrt((bx * bx) + (by * by));
	ti = Math.sqrt((bh * bh) + (bz * bz));
	dec = Math.atan2(by, bx) * DEG;
	dip = Math.atan2(bz, bh) * DEG;
	return (dt >= 0) && (dt <= 5) ? dec : 10000 * dec;
}