function NEtoLL(north, east)
{
  // converts NGR easting and northing to lat, lon.
  // input metres, output radians
  var nX = Number(north);
  var eX = Number(east);
  var a = 6377563.396;       // OSI semi-major
  var b = 6356256.91;        // OSI semi-minor
  var e0 = 400000;           // easting of false origin
  var n0 = -100000;          // northing of false origin
  var f0 = 0.9996012717;     // OSI scale factor on central meridian
  var e2 = 0.0066705397616;  // OSI eccentricity squared
  var lam0 = -0.034906585039886591;  // OSI false east
  var phi0 = 0.85521133347722145;    // OSI false north
  var af0 = a * f0;
  var bf0 = b * f0;
  var n = (af0 - bf0) / (af0 + bf0);
  var Et = east - e0;
  var phid = InitialLat(north, n0, af0, phi0, n, bf0);
  var nu = af0 / (Math.sqrt(1 - (e2 * (Math.sin(phid) * Math.sin(phid)))));
  var rho = (nu * (1 - e2)) / (1 - (e2 * (Math.sin(phid)) * (Math.sin(phid))));
  var eta2 = (nu / rho) - 1;
  var tlat2 = Math.tan(phid) * Math.tan(phid);
  var tlat4 = Math.pow(Math.tan(phid), 4);
  var tlat6 = Math.pow(Math.tan(phid), 6);
  var clatm1 = Math.pow(Math.cos(phid), -1);
  var VII = Math.tan(phid) / (2 * rho * nu);
  var VIII = (Math.tan(phid) / (24 * rho * (nu * nu * nu))) * (5 + (3 * tlat2) + eta2 - (9 * eta2 * tlat2));
  var IX = ((Math.tan(phid)) / (720 * rho * Math.pow(nu, 5))) * (61 + (90 * tlat2) + (45 * Math.pow(Math.tan(phid), 4) ));
  var phip = (phid - ((Et * Et) * VII) + (Math.pow(Et, 4) * VIII) - (Math.pow(Et, 6) * IX)); 
  var X = Math.pow(Math.cos(phid), -1) / nu;
  var XI = (clatm1 / (6 * (nu * nu * nu))) * ((nu / rho) + (2 * (tlat2)));
  var XII = (clatm1 / (120 * Math.pow(nu, 5))) * (5 + (28 * tlat2) + (24 * tlat4));
  var XIIA = clatm1 / (5040 * Math.pow(nu, 7)) * (61 + (662 * tlat2) + (1320 * tlat4) + (720 * tlat6));
  var lambdap = (lam0 + (Et * X) - ((Et * Et * Et) * XI) + (Math.pow(Et, 5) * XII) - (Math.pow(Et, 7) * XIIA));

  return { latitude: phip, longitude: lambdap };
} 

function Marc(bf0, n, phi0, phi)
{
	var Marc = bf0 * (((1 + n + ((5 / 4) * (n * n)) + ((5 / 4) * (n * n * n))) * (phi - phi0))
		- (((3 * n) + (3 * (n * n)) + ((21 / 8) * (n * n * n))) * (Math.sin(phi - phi0)) * (Math.cos(phi + phi0)))
		+ ((((15 / 8) * (n * n)) + ((15 / 8) * (n * n * n))) * (Math.sin(2 * (phi - phi0))) * (Math.cos(2 * (phi + phi0))))
		- (((35 / 24) * (n * n * n)) * (Math.sin(3 * (phi - phi0))) * (Math.cos(3 * (phi + phi0)))));
	
	return Marc;
}

function InitialLat(north, n0, af0, phi0, n, bf0)
{
	var phi1 = ((north - n0) / af0) + phi0;
	var M = Marc(bf0, n, phi0, phi1);
	var phi2 = ((north - n0 - M) / af0) + phi1;

	for(var i = 0; (Math.abs(north - n0 - M) > 0.00001) && (i < 20); i++) {
		phi2 = ((north - n0 - M) / af0) + phi1;
		M = Marc(bf0, n, phi0, phi2);
		phi1 = phi2;
	}

	return phi2;  
}

function ngr2ings(ngr)
{
	var east  = 0;
	var north = 0;

	if(/^W[AV][0-]/.test(ngr)) {
		var nci;
		var eci = "5" + ngr.substring(2,7);
		if (ngr.charAt(1) == "V")
			nci = "54" + ngr.substring(7,12);
		else
			nci = "55" + ngr.substring(7,12);
		east = Number(eci);
		north = Number(nci);
	} else {
		north = Number(ngr.substring(7));
		east = Number(ngr.substring(2,7));

		var t1 = ngr.charCodeAt(0) - 65;

		if(t1 < 0)   // probably a space, so Irish
			t1 = 18;    // S assumed for Irish (83-65)
		if(t1 > 8)
			t1 -= 1;

		var t2 = Math.floor(t1 / 5);
		north = north + 500000 * (3 - t2);
		east = east + 500000 * (t1 - 5 * t2 - 2);

		t1 = ngr.charCodeAt(1) - 65;
		if (t1 > 8)
			t1 -= 1;

		t2 = Math.floor(t1 / 5);
		north = north + 100000 * ( 4 - t2);
		east = east + 100000 * ( t1 - 5 * t2);
	}

	return {north: north, east: east};
}

function ngr2ll(ngr)
{
	var t;

	ngr = ngr.replace(/\s+/, '');

	switch(ngr.length) {
		case 4:
			t = ngr.substring(0,3) + "0000" + ngr.substring(3) + "0000";
			break;
		case 6:
			t = ngr.substring(0,4) + "000" + ngr.substring(4) + "000";
			break;
		case 8:
			t = ngr.substring(0,5) + "00" + ngr.substring(5) + "00";
			break;
		case 10:
			t = ngr.substring(0,6) + "0" + ngr.substring(6) + "0";
			break;
		case 12:
			t = ngr;
			break;
		default:
			return {latitude: 0, longitude: 0};
	}
	ngr = t;

	var ne = ngr2ings(ngr);
	var ll = NEtoLL(ne.north, ne.east);

	//var deg2rad = Math.PI / 180;
	var rad2deg = 180.0 / Math.PI;
	return {
		latitude:  ll.latitude * rad2deg,
		longitude: ll.longitude * rad2deg
	};
}
