// This shows range rings on the map. Interval is user choice or automatic
// Bill Chadwick Jan 2008

// point is the centre or origin of the rings which are drawn with colour, weight (width in pixels) and opacity (0-1).
// The interval parameter is either
//	1 - null in which case the interval is automatic depending on zoom level, showing several rings on screen
//	2 - A single number specifying a fixed interval between rings in metres
//	3 - An array of ring spacings in metres
// MaxRings is used for auto and fixed interval to limit the number of rings drawn, not used with an interval array
// if it is null, rings will be drawn to twice the map's diagonal
// if user labels are wanted, labels should be an array of length maxRings or the length of the interval array
// if interval is an array, colour can be a matching length array

function BdccRangeRings(point, colour, weight, opacity, interval, maxRings, labels) {

  this.colour = colour || "#0000FF";
  this.weight = weight || 3;
  this.opacity = opacity || 0.7;
  this.centre = point;
  this.interval = interval;
  this.maxRings = maxRings;
  this.labels = labels;

}
BdccRangeRings.prototype = new GOverlay();

BdccRangeRings.prototype.initialize = function(map) {

  this.map = map;
  this.circles = new Array();
  this.divs = new Array();//labels

  this.drawFirst = true;
  this.lstnMove = null;
  this.lstnStart = null;
  this.lstnType = null;

}

BdccRangeRings.prototype.remove = function() {

  	this.unDraw();

	//remove handlers we use to trigger redraw / undraw
	try{
	    if(this.lstnMove != null)
		    GEvent.removeListener(this.lstnMove);
	    if(this.lstnStart != null)
		    GEvent.removeListener(this.lstnStart);
	    if(this.lstnType != null)
		    GEvent.removeListener(this.lstnType);
		}
    catch(ex)
    {
    }

}

BdccRangeRings.prototype.unDraw = function() {

  var div = this.map.getPane(G_MAP_MARKER_SHADOW_PANE);

  try{

	var i = 0;
	for(i=0; i< this.circles.length; i++)
		this.map.removeOverlay(this.circles[i]);
  }
  catch(e){
  }

  try{

	var i = 0;
      for(i=0; i< this.divs.length; i++)
	      div.removeChild(this.divs[i]);
  }
  catch(e){
  }
  this.circles = new Array();
  this.divs = new Array();

}

BdccRangeRings.prototype.copy = function() {
  return new BdccRangeRings(this.point, this.colour, this.weight, this.opacity, this.interval, this.maxRings, this.labels);
}

//This normally does nothing due to reentrancy problems and problems removing overlays from within an overlay
//Instead we use the moveend event to trigger a redraw, this event occurs after zoom and map type changes
BdccRangeRings.prototype.redraw = function(force) {

	//but draw it the very first time
	if(this.drawFirst)
	{
		this.safeRedraw();

  		// We use the moveend event to trigger the redraw
  		var rdrw = GEvent.callback(this,this.safeRedraw );
  		this.lstnMove = GEvent.addListener(this.map,"moveend",function(){rdrw ();});

  		// We use the map type change event to trigger a redraw too
  		var rdrw2 = GEvent.callback(this,this.safeRedraw );
  		this.lstnType = GEvent.addListener(this.map,"maptypechanged",function(){rdrw2 ();});

		// And undraw during moves - for speed
  		var udrw = GEvent.callback(this,this.unDraw );
  		this.lstnStart = GEvent.addListener(this.map,"movestart",function(){udrw ();});

		this.drawFirst = false;

	}
}


// Redraw the rings based on the current projection and zoom level
BdccRangeRings.prototype.safeRedraw = function() {

  //clear old
  this.unDraw();

  var bnds = this.map.getBounds();
  var sz = this.map.getSize();
  var pxDiag = Math.sqrt((sz.width*sz.width) + (sz.height*sz.height));
  var diagKm = bnds.getNorthEast().distanceFrom(bnds.getSouthWest()) / 1000.0;
  var pxPerKm = pxDiag/diagKm;
  var d;//km initial/min interval

  if(this.interval == null)
  {
  	//auto interval

	var width = sz.width / pxPerKm ;
  	var height = sz.height / pxPerKm;
	var ww = Math.max(width,height);

  	if(ww < 1.0)
		d = 0.1;
  	else if(ww < 2.0)
		d = 0.2;
  	else if(ww < 5.0)
		d = 0.5;
  	else if(ww < 10.0)
		d = 1.0;
  	else if(ww < 20.0)
    		d = 2.0;
  	else if(ww < 50.0)
    		d = 5.0;
  	else if(ww < 100.0)
    		d = 10.0;
  	else if(ww < 200.0)
    		d = 20.0;
  	else if(ww < 500.0)
    		d = 50.0;
  	else if(ww < 1000.0)
    		d = 100.0;
  	else if(ww < 2000.0)
    		d = 200.0;
  	else if(ww < 5000.0)
    		d = 500.0;
  	else
    		d = 1000.0;
  }
  else if (this.interval.constructor.toString().indexOf("Array") == -1)
  	d = this.interval / 1000.0;// to km
  else
	d = this.interval;//array of distances


  if (d.constructor.toString().indexOf("Array") == -1){

      var p = (d >= 10.0) ? 0 : 1;//precision for label distances in km

	if(this.maxRings == null){
		//draw the rings to no more than twice the screen diagonal, auto or fixed interval
		// no custom labels as # rings drawn varies
		for(var r = d; (r < (diagKm * 2)); r += d)
 		  this.drawCircle (r ,r.toFixed(p) + "km",this.colour);
	}
	else{
		//draw maxRings rings auto or fixed interval
		var r = d;
		for(var i = 0; i<this.maxRings; i++){
 		  this.drawCircle (r ,(this.labels == null)?r.toFixed(p)+ "km":this.labels[i],this.colour);
		  r += d;
		}
	}
  }
  else{
	// interval array used
	for(var i = 0; i<d.length; i++){
	    var c;
	    if (this.colour.constructor.toString().indexOf("Array") == -1)
	        c = this.colour;
	    else
		  c = this.colour[i];
 	    this.drawCircle (d[i]/1000.0 ,(this.labels == null)?d[i].toFixed(p)+ "km":this.labels[i],c);
	}

  }

}

  // get a new GLatLng distanceMeters away on the compass bearing azimuthDegrees
  // from the GLatLng point - accurate to better than 200m in 140km (20m in 14km) in the UK

  BdccRangeRings.prototype.PointAtRangeAndBearing = function (point, distanceMeters, azimuthDegrees)
        {
             var latr = point.lat() * Math.PI / 180.0;
             var lonr = point.lng() * Math.PI / 180.0;

             var coslat = Math.cos(latr);
             var sinlat = Math.sin(latr);
             var az = azimuthDegrees* Math.PI / 180.0;
             var cosaz = Math.cos(az);
             var sinaz = Math.sin(az);
             var dr = distanceMeters / 6378137.0; // distance in radians using WGS84 Equatorial Radius
             var sind = Math.sin(dr);
             var cosd = Math.cos(dr);

            return new GLatLng(Math.asin((sinlat * cosd) + (coslat * sind * cosaz)) * 180.0 / Math.PI,
            (Math.atan2((sind * sinaz), (coslat * cosd) - (sinlat * sind * cosaz)) + lonr) * 180.0 / Math.PI);
        }



BdccRangeRings.prototype.drawCircle = function (rKm, label, colour)
{
  if (r < 0)
	return;

  if (r > (6378137.0 * Math.PI * 2))
	return;

  var mapDiv = this.map.getPane(G_MAP_MARKER_SHADOW_PANE);
  var c;
  var b;
  var pts = new Array();
  var r = rKm * 1000.0;

  // use 40 segment polyline to approximate a circle

  for(b=0; b<=360; b+=9){

	pts.push(this.PointAtRangeAndBearing(this.centre,r,b));

      if ((b %45 == 0)&&(b!=360)){

		//draw a label every 45 degrees

		var p = this.map.fromLatLngToDivPixel(pts[pts.length-1]);
		var dv = document.createElement("DIV");
		mapDiv.insertBefore(dv,null);
		dv.style.position = "absolute";
		dv.style.border = "none";
		dv.style.color = colour;
		dv.style.fontFamily='Arial';
		dv.style.fontSize='x-small';
		dv.innerHTML = label;
		var dx;
		var dy;

		//offset the labels to be just outside the ring

		if(b==0) {
			dx = -dv.offsetWidth * 0.5;
			dy = -dv.offsetHeight;
		}
		else if(b == 45){
			dx = 3;
			dy = -dv.offsetHeight;
		}
		else if(b == 90){
			dx = 3;
			dy = -dv.offsetHeight * 0.5;
		}
		else if(b == 135){
			dx = 3;
			dy = 3;
		}
		else if(b == 180){
			dx = -dv.offsetWidth * 0.5;
			dy = 3;
		}
		else if(b == 225){
			dx = -dv.offsetWidth -3;
			dy = 3;
		}
		else if(b == 270){
			dx = -dv.offsetWidth -3;
			dy = -dv.offsetHeight * 0.5;
		}
		else if(b == 315){
			dx = -dv.offsetWidth -3;
			dy = -dv.offsetHeight;
		}

		dv.style.left = (p.x+dx).toString() + "px";
		dv.style.top = (p.y+dy).toString() + "px";

		this.divs.push(dv);
	}
  }

  var c = new GPolyline(pts,colour,this.weight,this.opacity);
  this.map.addOverlay(c);
  this.circles.push(c);

}


/***FIN**/
/**LatLngToOSGB**/

function OGBLatLng(lat, lon)
{
	this.lat = lat;
	this.lon = lon;
}

function WGS84LatLng(lat, lon)
{
	this.lat = lat;
	this.lon = lon;
}

function OGBNorthEast(east, north)
{
	this.north = north;
	this.east = east;
}

function OGBRect(bottomLeft, topRight)
{
	this.bl = bottomLeft;
	this.tr = topRight;
}



//convert WGS84 Latitude and Longitude to Ordnance Survey 1936 Latitude Longitude

function WGS84ToOGB(WGlat, WGlon, height)
{
var deg2rad = Math.PI / 180;
var rad2deg = 180.0 / Math.PI;

//first off convert to radians
var radWGlat = WGlat * deg2rad;
var radWGlon = WGlon * deg2rad;
//these are the values for WGS84(GRS80) to OSGB36(Airy)
var a = 6378137; // WGS84_AXIS
var e = 0.00669438037928458; // WGS84_ECCENTRIC
var h = height; // height above datum (from $GPGGA sentence)
var a2 = 6377563.396; // OSGB_AXIS
var e2 = 0.0066705397616; // OSGB_ECCENTRIC
var xp = -446.448;
var yp = 125.157;
var zp = -542.06;
var xr = -0.1502;
var yr = -0.247;
var zr = -0.8421;
var s = 20.4894;

// convert to cartesian; lat, lon are in radians
var sf = s * 0.000001;
var v = a / (Math.sqrt(1 - (e * (Math.sin(radWGlat) * Math.sin(radWGlat)))));
var x = (v + h) * Math.cos(radWGlat) * Math.cos(radWGlon);
var y = (v + h) * Math.cos(radWGlat) * Math.sin(radWGlon);
var z = ((1 - e) * v + h) * Math.sin(radWGlat);

// transform cartesian
var xrot = (xr / 3600) * deg2rad;
var yrot = (yr / 3600) * deg2rad;
var zrot = (zr / 3600) * deg2rad;
var hx = x + (x * sf) - (y * zrot) + (z * yrot) + xp;
var hy = (x * zrot) + y + (y * sf) - (z * xrot) + yp;
var hz = (-1 * x * yrot) + (y * xrot) + z + (z * sf) + zp;

// Convert back to lat, lon
var newLon = Math.atan(hy / hx);
var p = Math.sqrt((hx * hx) + (hy * hy));
var newLat = Math.atan(hz / (p * (1 - e2)));
v = a2 / (Math.sqrt(1 - e2 * (Math.sin(newLat) * Math.sin(newLat))));
var errvalue = 1.0;
var lat0 = 0;
while (errvalue > 0.001)
{
lat0 = Math.atan((hz + e2 * v * Math.sin(newLat)) / p);
errvalue = Math.abs(lat0 - newLat);
newLat = lat0;
}

//convert back to degrees
newLat = newLat * rad2deg;
newLon = newLon * rad2deg;

return new OGBLatLng(newLat, newLon);

}


//convert Ordnance Survey 1936 Latitude Longitude to WGS84 Latitude and Longitude

function OGBToWGS84(OGlat, OGlon, height)
{
var deg2rad = Math.PI / 180;
var rad2deg = 180.0 / Math.PI;

//first off convert to radians
var radOGlat = OGlat * deg2rad;
var radOGlon = OGlon * deg2rad;
//these are the values for WGS84(GRS80) to OSGB36(Airy)

var a2 = 6378137; // WGS84_AXIS
var e2 = 0.00669438037928458; // WGS84_ECCENTRIC

var h = height; // height above datum (from $GPGGA sentence)

var a = 6377563.396; // OSGB_AXIS
var e = 0.0066705397616; // OSGB_ECCENTRIC

var xp = 446.448;
var yp = -125.157;
var zp = 542.06;
var xr = 0.1502;
var yr = 0.247;
var zr = 0.8421;

var s = -20.4894;

// convert to cartesian; lat, lon are in radians
var sf = s * 0.000001;
var v = a / (Math.sqrt(1 - (e * (Math.sin(radOGlat) * Math.sin(radOGlat)))));
var x = (v + h) * Math.cos(radOGlat) * Math.cos(radOGlon);
var y = (v + h) * Math.cos(radOGlat) * Math.sin(radOGlon);
var z = ((1 - e) * v + h) * Math.sin(radOGlat);

// transform cartesian
var xrot = (xr / 3600) * deg2rad;
var yrot = (yr / 3600) * deg2rad;
var zrot = (zr / 3600) * deg2rad;
var hx = x + (x * sf) - (y * zrot) + (z * yrot) + xp;
var hy = (x * zrot) + y + (y * sf) - (z * xrot) + yp;
var hz = (-1 * x * yrot) + (y * xrot) + z + (z * sf) + zp;

// Convert back to lat, lon
var newLon = Math.atan(hy / hx);
var p = Math.sqrt((hx * hx) + (hy * hy));
var newLat = Math.atan(hz / (p * (1 - e2)));
v = a2 / (Math.sqrt(1 - e2 * (Math.sin(newLat) * Math.sin(newLat))));
var errvalue = 1.0;
var lat0 = 0;
while (errvalue > 0.001)
{
lat0 = Math.atan((hz + e2 * v * Math.sin(newLat)) / p);
errvalue = Math.abs(lat0 - newLat);
newLat = lat0;
}

//convert back to degrees
newLat = newLat * rad2deg;
newLon = newLon * rad2deg;

return new WGS84LatLng(newLat, newLon);

}


//converts lat and lon (OSGB36) to OS northings and eastings
function LLtoNE(lat, lon)
{
var deg2rad = Math.PI / 180;
var rad2deg = 180.0 / Math.PI;

var phi = lat * deg2rad; // convert latitude to radians
var lam = lon * deg2rad; // convert longitude to radians
var a = 6377563.396; // OSGB semi-major axis
var b = 6356256.91; // OSGB semi-minor axis
var e0 = 400000; // easting of false origin
var n0 = -100000; // northing of false origin
var f0 = 0.9996012717; // OSGB scale factor on central meridian
var e2 = 0.0066705397616; // OSGB eccentricity squared
var lam0 = -0.034906585039886591; // OSGB false east
var phi0 = 0.85521133347722145; // OSGB false north
var af0 = a * f0;
var bf0 = b * f0;

// easting
var slat2 = Math.sin(phi) * Math.sin(phi);
var nu = af0 / (Math.sqrt(1 - (e2 * (slat2))));
var rho = (nu * (1 - e2)) / (1 - (e2 * slat2));
var eta2 = (nu / rho) - 1;
var p = lam - lam0;
var IV = nu * Math.cos(phi);
var clat3 = Math.pow(Math.cos(phi), 3);
var tlat2 = Math.tan(phi) * Math.tan(phi);
var V = (nu / 6) * clat3 * ((nu / rho) - tlat2);
var clat5 = Math.pow(Math.cos(phi), 5);
var tlat4 = Math.pow(Math.tan(phi), 4);
var VI = (nu / 120) * clat5 * ((5 - (18 * tlat2)) + tlat4 + (14 * eta2) - (58 * tlat2 * eta2));
var east = e0 + (p * IV) + (Math.pow(p, 3) * V) + (Math.pow(p, 5) * VI);

// northing
var n = (af0 - bf0) / (af0 + bf0);
var M = Marc(bf0, n, phi0, phi);
var I = M + (n0);
var II = (nu / 2) * Math.sin(phi) * Math.cos(phi);
var III = ((nu / 24) * Math.sin(phi) * Math.pow(Math.cos(phi), 3)) * (5 - Math.pow(Math.tan(phi), 2) + (9 * eta2));
var IIIA = ((nu / 720) * Math.sin(phi) * clat5) * (61 - (58 * tlat2) + tlat4);
var north = I + ((p * p) * II) + (Math.pow(p, 4) * III) + (Math.pow(p, 6) * IIIA);

// make whole number values
east = Math.round(east); // round to whole number of meters
north = Math.round(north);

return new OGBNorthEast(east, north);
}


//convert northing and easting to letter and number grid system
function NE2NGR( east,  north)
{
east = Math.round(east);
north = Math.round(north);
var eX = east / 500000;
var nX = north / 500000;
var tmp = Math.floor(eX) - 5.0 * Math.floor(nX) + 17.0;
nX = 5 * (nX - Math.floor(nX));
eX = 20 - 5.0 * Math.floor(nX) + Math.floor(5.0 * (eX - Math.floor(eX)));
if (eX > 7.5) eX = eX + 1; // I is not used
if (tmp > 7.5) tmp = tmp + 1; // I is not used

var eing = east - (Math.floor(east / 100000)*100000);
var ning = north - (Math.floor(north / 100000)*100000);
var estr = eing.toString();
var nstr = ning.toString();
while(estr.length < 5)
	estr = "0" + estr;
while(nstr.length < 5)
	nstr = "0" + nstr;

var ngr = String.fromCharCode(tmp + 65) +
          String.fromCharCode(eX + 65) +
          " " + estr + " " + nstr;

return ngr;
}

//helper
function Marc( bf0,  n,  phi0,  phi)
{
return 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)))));
}

function NEtoLL (east, north){

//metres in, degrees out

var K0 = 0.9996012717; // grid scale factor on central meridean
var OriginLat = 49.0;
var OriginLong = -2.0;
var OriginX = 400000; // 400 kM
var OriginY = -100000; // 100 kM
var a = 6377563.396; // Airy Spheroid
var b = 6356256.910;

var 	e2;
var 	ex;
var 	n1;
var 	n2;
var 	n3;
var 	OriginNorthings;


// compute interim values
a = a * K0;
b = b * K0;

n1 = (a - b)/(a + b);
n2 = n1 * n1;
n3 = n2 * n1;

lat = OriginLat * Math.PI / 180.0; // to radians


e2 = (a*a - b*b)/(a*a);  // first eccentricity
ex = (a*a - b*b)/(b*b);  // second eccentricity


OriginNorthings = b*lat + b*(n1*(1.0 + 5.0*n1*(1.0+n1)/4.0)*lat
					- 3.0*n1*(1.0+n1*(1.0+7.0*n1/8.0))*Math.sin(lat)*Math.cos(lat)
					+ (15.0*n1*(n1+n2)/8.0)*Math.sin(2.0*lat)*Math.cos(2.0*lat)
					- (35.0*n3/24.0)*Math.sin(3.0*lat)*Math.cos(3.0*lat) );

var OriginLat = 49.0;
var OriginLong = -2.0;
var OriginX = 400000; // 400 kM
var OriginY = -100000; // 100 kM


var lat;    // what we calculate
var lon;

var northing = north - OriginY;
var easting = east - OriginX;

var nu, phid, phid2, t2, t, q2, c, s, nphid, dnphid; // temps
var nu2, nudivrho, invnurho, rho, eta2;


/* Evaluate M term: latitude of the northing on the centre meridian */

northing += OriginNorthings;

phid = northing / (b*(1.0 + n1 + 5.0*(n2+n3)/4.0)) - 1.0;
phid2 = phid + 1.0;

while (Math.abs(phid2 - phid) > 1E-6)
   {
   phid = phid2;
   nphid = b*phid + b*(n1*(1.0 + 5.0*n1*(1.0+n1)/4.0)*phid
           - 3.0*n1*(1.0+n1*(1.0+7.0*n1/8.0))*Math.sin(phid)*Math.cos(phid)
           + (15.0*n1*(n1+n2)/8.0)*Math.sin(2.0*phid)*Math.cos(2.0*phid)
           - (35.0*n3/24.0)*Math.sin(3.0*phid)*Math.cos(3.0*phid) );

   dnphid = b*((1.0+n1+5.0*(n2+n3)/4.0)-3.0*(n1+n2+7.0*n3/8.0)*Math.cos(2.0*phid)
            +(15.0*(n2+n3)/4.0)*Math.cos(4*phid)-(35.0*n3/8.0)*Math.cos(6.0*phid));

   phid2 = phid - (nphid - northing)/dnphid;
   }

c  = Math.cos(phid);
s  = Math.sin(phid);
t  = Math.tan(phid);
t2 = t*t;
q2 = easting*easting;


nu2 = (a*a)/(1.0 - e2*s*s);
nu = Math.sqrt(nu2);

nudivrho = a*a*c*c/(b*b) - c*c + 1.0;

eta2 = nudivrho - 1;

rho = nu / nudivrho;

invnurho = ((1.0-e2*s*s)*(1.0-e2*s*s))/(a*a*(1.0-e2));

lat = phid - t*q2*invnurho/2.0 + (q2*q2*(t/(24*rho*nu2*nu)*(5 + (3*t2) + eta2 -(9*t2*eta2))));

lon = (easting/(c*nu))
        - (easting*q2*((nudivrho+2.0*t2)/(6.0*nu2))/(c*nu))
		+ (q2*q2*easting*(5 + (28*t2) + (24*t2*t2))/(120*nu2*nu2*nu*c));

return new OGBLatLng(lat * 180.0 / Math.PI,(lon * 180.0 / Math.PI) + OriginLong);

}

//return rect in OSGB N/E coords that encloses the given wgs84 rect
function enclosingOsgbRect(WGleft,WGbottom,WGtop,WGright){

	var blOGB = WGS84ToOGB(WGbottom,WGleft,0);
	var trOGB = WGS84ToOGB(WGtop,WGright,0);
	var brOGB = WGS84ToOGB(WGbottom,WGright,0);
	var tlOGB = WGS84ToOGB(WGtop,WGleft,0);

	var blEN = LLtoNE(blOGB.lat,blOGB.lon);
	var trEN = LLtoNE(trOGB.lat,trOGB.lon);
	var brEN = LLtoNE(brOGB.lat,brOGB.lon);
	var tlEN = LLtoNE(tlOGB.lat,tlOGB.lon);

	var e = Math.min(blEN.east,tlEN.east);
	var w = Math.max(brEN.east,trEN.east);
	var s = Math.min(blEN.north,brEN.north);
	var n = Math.max(trEN.north,tlEN.north);

	return new OGBRect(new OGBNorthEast(e,s),new OGBNorthEast(w,n));
}

function OsgbConvergence(lat,lon)
{

var K0 = 0.9996012717; // grid scale factor on central meridean
var a = 6377563.396; // Airy Spheroid
var b = 6356256.910;
var oDeg = -2.0;//longitude origin
a = a * K0;
b = b * K0;

/* Compute convergence, OS Geodetic Information Paper No 1 */
var phi = lat * Math.PI / 180.0;
var c = Math.cos(phi);
var s = Math.sin(phi);
var t = Math.tan(phi);
var t2 = t*t;
var p = (lon - oDeg) * Math.PI / 180.0;
var eta2 = a*a*c*c/(b*b) - c*c;

return (
( p*s ) +
( p*p*p * s*c*c * (1.0 + (3.0*eta2) + (2.0*eta2*eta2)) / 3.0 ) +
( p*p*p*p*p * s*c*c*c*c * (2.0 - t2) / 15.0 )
)
* 180.0 / Math.PI;

}

function NGR2NE(ngr){
var e;
var n;

ngr = ngr.toUpperCase(ngr);

var bits = ngr.split(' ');
ngr = "";
for(var i=0;i<bits.length;i++)
    ngr+=bits[i];

var c = ngr.charAt(0);
if (c =='S'){
    e = 0;
    n = 0;
    }
else if (c == 'T'){
    e = 500000;
    n = 0;
    }
else if (c == 'N'){
    n = 500000;
    e = 0;
    }
else if (c == 'O'){
    n = 500000;
    e = 500000;
    }
else if(c == 'H'){
    n = 1000000;
    e = 0;
    }
else
    return null;

c = ngr.charAt(1);
if(c == 'I')
    return null;

c = ngr.charCodeAt(1) - 65;
if(c > 8)
    c -= 1;
e += (c % 5) * 100000;
n += (4 - Math.floor(c/5)) * 100000;

c = ngr.substr(2);
if ((c.length%2) == 1)
    return null;
if (c.length > 10)
    return null;

try{
    var s = c.substr(0,c.length/2);
    while(s.length < 5)
        s += '0';
    e += parseInt(s,10);
    if(isNaN(e))
        return null;

    s = c.substr(c.length/2);
    while(s.length < 5)
        s += '0';
    n += parseInt(s,10);
    if(isNaN(n))
        return null;

    return new OGBNorthEast(e,n);
    }
catch (ex)
{
    return null;
}

}
/**FIN**/
/**LatLonGraticule.js**/
// This shows a lat/lon graticule on the map. Interval is automatic
// As first seen at www.bdcc.co.uk
// Bill Chadwick 2006

function LatLonGraticule(sexagesimal) {
    this.sex_ = sexagesimal || false;//default is decimal intervals
}
LatLonGraticule.prototype = new GOverlay();

LatLonGraticule.prototype.initialize = function(map) {

  //save for later
  this.map_ = map;
  //array for divs used for lines and labels
  this.divs_ = new Array();

}

LatLonGraticule.prototype.remove = function() {

  try{
  var i = 0;
  var div = this.map_.getPane(G_MAP_MARKER_SHADOW_PANE);
  for(i=0; i< this.divs_.length; i++)
	div.removeChild(this.divs_[i]);
	}
  catch(e){
  }

}

LatLonGraticule.prototype.copy = function() {
  return new LatLonGraticule();
}

// Redraw the graticule based on the current projection and zoom level
LatLonGraticule.prototype.redraw = function(force) {

  //clear old
  this.remove();

  //best color for writing on the map
  this.color_ = this.map_.getCurrentMapType().getTextColor();

  //determine graticule interval
  var bnds = this.map_.getBounds();

  var l = bnds.getSouthWest().lng();
  var b = bnds.getSouthWest().lat();
  var t = bnds.getNorthEast().lat();
  var r = bnds.getNorthEast().lng();

  //sanity
  if (b < -90.0)
	b = -90.0;
  if(t > 90.0)
	t = 90.0;
  if(l < -180.0)
    l = -180.0;
  if(r > 180.0)
    r = 180.0;

  if(l == r){
	l = -180.0;
	r = 180.0;
  }

  if(t == b){
	b = -90.0;
	t = 90.0;
  }

  //grid interval in minutes
  var dLat = this.gridIntervalMins(t-b);
  var dLng;
  if(r>l)
	dLng = this.gridIntervalMins(r-l);
  else
    dLng = this.gridIntervalMins((180-l)+(r+180));

  //round iteration limits to the computed grid interval
  l = Math.floor(l*60/dLng)*dLng/60;
  b = Math.floor(b*60/dLat)*dLat/60;
  t = Math.ceil(t*60/dLat)*dLat/60;
  r = Math.ceil(r*60/dLng)*dLng/60;

  //Sanity
  if (b <= -90.0)
	b = -90;
  if(t >= 90.0)
	t = 90;
  if(l < -180.0)
    l = -180.0;
  if(r > 180.0)
    r = 180.0;

  //to whole degrees
  dLat /= 60;
  dLng /= 60;

  //# digits after DP for decimal labels
  var latDecs = this.gridPrecision(dLat);
  var lonDecs = this.gridPrecision(dLng);

  this.divs_ = new Array();
  var i=0;//count inserted divs

  //min and max x and y pixel values for graticule lines
  var pbl = this.map_.fromLatLngToDivPixel(new GLatLng(b,l));
  var ptr = this.map_.fromLatLngToDivPixel(new GLatLng(t,r));

  this.maxX = ptr.x;
  this.maxY = pbl.y;
  this.minX = pbl.x;
  this.minY = ptr.y;

  var x;//coord for label
  //labels on second column to avoid peripheral controls
  var y = this.map_.fromLatLngToDivPixel(new GLatLng(b+dLat+dLat,l)).y + 2;//coord for label

  //pane/layer to write on
  var mapDiv = this.map_.getPane(G_MAP_MARKER_SHADOW_PANE);

  var lo = l;//copy to save original

  if(r<lo)
      r += 360.0;

  //vertical lines
  while(lo<=r){

	var p = this.map_.fromLatLngToDivPixel(new GLatLng(b,lo));

	//line
	this.divs_[i] = this.createVLine(p.x);
	mapDiv.insertBefore(this.divs_[i],null);
	i++;

	//label
	var d = document.createElement("DIV");
	x = p.x + 3;
	d.style.position = "absolute";
    d.style.left = x.toString() + "px";
    d.style.top = y.toString() + "px";
	d.style.color = this.color_;
	d.style.fontFamily='Arial';
	d.style.fontSize='x-small';
	if(this.sex_){
		var degs = Math.floor(Math.abs(lo));
		var mins = ((Math.abs(lo)-degs)*60.0).toFixed(2);
		if(mins == "60.00"){
			degs += 1.0;
			mins = "0.00";
			}
		d.innerHTML= degs + ":" + mins;
        }
    else{
        d.innerHTML = (Math.abs(lo)).toFixed(lonDecs);// only significant digits
        }
	if(lo<0)
	d.title = "West (WGS84)";
	else
	d.title = "East (WGS84)";
	mapDiv.insertBefore(d,null);

	this.divs_[i] = d;//save for remove

	i++;// next
	lo += dLng;
	if (lo > 180.0){
		r -= 360.0;
		lo -= 360.0;
	}

  }

  var j = 0;// count lines

  //labels on second row to avoid controls
  x = this.map_.fromLatLngToDivPixel(new GLatLng(b,l+dLng+dLng)).x + 3;

  //horizontal lines
  while(b<=t){

	var p = this.map_.fromLatLngToDivPixel(new GLatLng(b,l));

	//line
	if(r < l){ //draw lines across the dateline
		this.divs_[i] = this.createHLine3(b);
		mapDiv.insertBefore(this.divs_[i],null);
		i++;
		}
	else if (r == l){ //draw lines for world scale zooms
		this.divs_[i] = this.createHLine3(b);
		mapDiv.insertBefore(this.divs_[i],null);
		i++;
		}
	else{
		this.divs_[i] = this.createHLine(p.y);
		mapDiv.insertBefore(this.divs_[i],null);
		i++;
		}

	//label
	var d = document.createElement("DIV");
	y = p.y + 2;

	d.style.position = "absolute";
	d.style.left = x.toString() + "px";
	d.style.top = y.toString() + "px";
	d.style.color = this.color_;
	d.style.fontFamily='Arial';
	d.style.fontSize='x-small';
	if(this.sex_){
		var degs = Math.floor(Math.abs(b));
		var mins = ((Math.abs(b)-degs)*60.0).toFixed(2);
		if(mins == "60.00"){
			degs += 1.0;
			mins = "0.00";
			}
		d.innerHTML= degs + ":" + mins;
        }
    else{
        d.innerHTML = (Math.abs(b)).toFixed(latDecs);// only significant digits
        }
	if(b<0)
	d.title = "South (WGS84)";
	else
	d.title = "North (WGS84)";

	if(j != 2)//dont put two labels in the same place
	{
		mapDiv.insertBefore(d,null);
		this.divs_[i] = d; // save for remove
		i++;
	}

	j++;//next
	b += dLat;
  }

}

LatLonGraticule.prototype.gridIntervalMins = function(dDeg) {
    if(this.sex_)
	    return this.gridIntervalSexMins(dDeg)
    else
	    return this.gridIntervalDecMins(dDeg)
}

//calculate rounded graticule interval in decimals of degrees for supplied lat/lon span
//return is in minutes
LatLonGraticule.prototype.gridIntervalDecMins = function(dDeg) {

  var dDeg = dDeg/10;//want around 10 lines in the graticule
  dDeg *= 6000;//to minutes*100
  dDeg = Math.ceil(dDeg)/100;//minutes and hundredths of mins

  if(dDeg <= 0.06)
	dDeg = 0.06;//0.001 degrees
  else if(dDeg <= 0.12)
	dDeg = 0.12;//0.002 degrees
  else if(dDeg <= 0.3)
	dDeg = 0.3;//0.005 degrees
  else if(dDeg <= 0.6)
	dDeg = 0.6;//0.01 degrees
  else if (dDeg <=  1.2)
	dDeg = 1.2;//0.02 degrees
  else if(dDeg <= 3)
	dDeg = 3;//0.05 degrees
  else if(dDeg <= 6)
	dDeg = 6;//0.1 degrees
  else if (dDeg <=  12)
	dDeg = 12;//0.2 degrees
  else if (dDeg <=  30)
	dDeg = 30;//0.5
  else if (dDeg <=  60)
	dDeg = 60;//1
  else if (dDeg <=  (60*2))
	dDeg = 60*2;
  else if (dDeg <=  (60*5))
	dDeg = 60*5;
  else if (dDeg <=  (60*10))
	dDeg = 60*10;
  else if (dDeg <=  (60*20))
	dDeg = 60*20;
  else if (dDeg <=  (60*30))
	dDeg = 60*30;
  else
	dDeg = 60*45;

  return dDeg;
}

//calculate rounded graticule interval in Minutes for supplied lat/lon span
//return is in minutes
LatLonGraticule.prototype.gridIntervalSexMins = function(dDeg) {

  var dDeg = dDeg/10;//want around 10 lines in the graticule
  dDeg *= 6000;//to minutes*100
  dDeg = Math.ceil(dDeg)/100;//minutes and hundredths of mins

  if(dDeg <= 0.01)
        dDeg = 0.01;//0.01 minutes
  else if(dDeg <= 0.02)
        dDeg = 0.02;//0.02 minutes
  else if(dDeg <= 0.05)
        dDeg = 0.05;//0.05 minutes
  else if(dDeg <= 0.1)
        dDeg = 0.1;//0.1 minutes
  else if(dDeg <= 0.2)
        dDeg = 0.2;//0.2 minutes
  else if(dDeg <= 0.5)
        dDeg = 0.5;//0.5 minutes
  else if(dDeg <= 1.0)
        dDeg = 1.0;//1.0 minute
  else if(dDeg <= 3)
	dDeg = 3;//0.05 degrees
  else if(dDeg <= 6)
	dDeg = 6;//0.1 degrees
  else if (dDeg <=  12)
	dDeg = 12;//0.2 degrees
  else if (dDeg <=  30)
	dDeg = 30;//0.5
  else if (dDeg <=  60)
	dDeg = 60;//1
  else if (dDeg <=  (60*2))
	dDeg = 60*2;
  else if (dDeg <=  (60*5))
	dDeg = 60*5;
  else if (dDeg <=  (60*10))
	dDeg = 60*10;
  else if (dDeg <=  (60*20))
	dDeg = 60*20;
  else if (dDeg <=  (60*30))
	dDeg = 60*30;
  else
	dDeg = 60*45;

  return dDeg;

}

//calculate grid label precision from decimal grid interval in degrees
LatLonGraticule.prototype.gridPrecision = function(dDeg) {

if(dDeg < 0.01)
	return 3;
else if(dDeg < 0.1)
	return 2;
else if(dDeg < 1)
	return 1;
else return 0;

}

//returns a div that is a vertical single pixel line
LatLonGraticule.prototype.createVLine = function(x) {

	var div = document.createElement("DIV");
	div.style.position = "absolute";
	div.style.overflow = "hidden";
	div.style.backgroundColor = this.color_;
	div.style.left = x + "px";
	div.style.top = this.minY + "px";
	div.style.width = "1px";
	div.style.height = (this.maxY-this.minY) + "px";
    return div;

}

//returns a div that is a horizontal single pixel line
LatLonGraticule.prototype.createHLine = function(y) {

	var div = document.createElement("DIV");
	div.style.position = "absolute";
	div.style.overflow = "hidden";
	div.style.backgroundColor = this.color_;
	div.style.left = this.minX + "px";
	div.style.top = y + "px";
	div.style.width = (this.maxX-this.minX) + "px";
	div.style.height = "1px";
    return div;

}

//returns a div that is a horizontal single pixel line, across the dateline
//we find the start and width of a 180 degree line and draw the same amount
//to its left and right
LatLonGraticule.prototype.createHLine3 = function(lat) {

	var f = this.map_.fromLatLngToDivPixel(new GLatLng(lat,0));
	var t = this.map_.fromLatLngToDivPixel(new GLatLng(lat,180));
	var div = document.createElement("DIV");
	div.style.position = "absolute";
	div.style.overflow = "hidden";
	div.style.backgroundColor = this.color_;
	var x1 = f.x;
	var x2 = t.x;
	if(x2 < x1){
		x2 = f.x;
		x1 = t.x;
	}
	div.style.left = (x1-(x2-x1)) + "px";
	div.style.top = f.y + "px";
	div.style.width = ((x2-x1)*3) + "px";
	div.style.height = "1px";
    return div;

}
/**FIN**/
/**SunRiseSunSet**/
/**
*
* Calculates sunrise and sunset times. Based on
* NOAA's algorithm.
*
* The calculations in the NOAA Sunrise/Sunset and Solar Position Calculators
* are based on equations from Astronomical Algorithms, by Jean Meeus. The
* sunrise and sunset results have been verified to be accurate to within a
* minute for locations between +/- 72° latitude, and within 10 minutes outside
* of those latitudes.
*
* This is an implementation of NOAA's low accuracy calculations
*
* Tested against http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html
* Results within 2-3 minutes for places in UK, US and Australia
*
* This javascript port by Bill Chadwick, November 2008 - free for any use.
*
*/

function SunRiseSunSet() {

    /** Converts day of year to fractional year in radians.
    * @return fractional year (radians)
    */
    this.gamma = function(dayOfYear) {

        return ((2 * Math.PI) / 365.0) * (dayOfYear - 1);
    }

    // equation of time - minutes
    this.eqtimeMins = function (dayOfYear) {

        var g = this.gamma(dayOfYear);
        return 229.18 * (0.000075
        + (0.001868 * Math.cos(g))
        - (0.032077 * Math.sin(g))
        - (0.014615 * Math.cos(g * 2.0))
        - (0.040849 * Math.sin(g * 2.0))
        );
    }

    /** Calculates solar declination angle in radians.
    * @return solar declination angle (radians)
    */
    this.declRads = function (dayOfYear) {

        var g = this.gamma(dayOfYear);
        return 0.006918
        - 0.399912 * Math.cos(g)
        + 0.070257 * Math.sin(g)
        - 0.006758 * Math.cos(g * 2.0)
        + 0.000907 * Math.sin(g * 2.0)
        - 0.002697 * Math.cos(g * 3.0)
        + 0.00148 * Math.sin(g * 3.0);
    }

    // sunrise/sunset hour angle, +ha = sunrise, -ha = sunset

    this.haDegrees = function (latitudeDegrees, dayOfYear) {

        var la = latitudeDegrees * (Math.PI / 180.0);
        var decl = this.declRads(dayOfYear);
        return Math.acos(
        (Math.cos(90.833 * (Math.PI / 180.0)) / (Math.cos(la) * Math.cos(decl)))
        - (Math.tan(la) * Math.tan(decl))
        ) * (180.0 / Math.PI);
    }

    //Java Script date to day of year
    this.dayOfYear = function(d){
        var onejan = new Date(d.getUTCFullYear(),0,1);
        return Math.ceil((d - onejan) / 86400000);
    }

    //Java script adjust date from decimal UTC hours
    this.dateFromHours = function(hrs, d){
        var r = new Date(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate());
        var mins = (hrs-Math.floor(hrs)) * 60.0;
        hrs = Math.floor(hrs);
        if (hrs >= 24){
            r = new Date(r.getTime() + (24 * 60 * 60 * 1000));
            hrs -= 24;
        }
        else if (hrs < 0){
            r = new Date(r.getTime() - (24 * 60 * 60 * 1000));
            hrs += 24;
        }
        else{
            r = new Date(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate(),hrs,Math.round(mins),0);
        }
        r.setUTCHours(hrs);
        r.setUTCMinutes(Math.round(mins));

        return r;
    }

// Public methods use - for West and South

    this.sunRise = function(latitudeDegrees, longitudeDegrees, jsDate) {

        var doy = this.dayOfYear(jsDate);
        var hrs = (720.0
        + (4 * (-longitudeDegrees - this.haDegrees(latitudeDegrees, doy)))
        - this.eqtimeMins(doy)
        ) / 60.0;
        // -ve hours means the previous day, > 24 hrs means the next day
        return this.dateFromHours(hrs,jsDate);
    }

    this.sunSet = function(latitudeDegrees, longitudeDegrees, jsDate) {

        var doy = this.dayOfYear(jsDate);
        var hrs = (720.0
        + (4 * (-longitudeDegrees + this.haDegrees(latitudeDegrees, doy)))
        - this.eqtimeMins(doy)
        ) / 60.0;
        // -ve hours means the previous day, > 24 hrs means the next day
        return this.dateFromHours(hrs,jsDate);
    }

    this.sunNoon = function(latitudeDegrees, longitudeDegrees, jsDate) {

        var hrs = (720.0
        + (4 * (-longitudeDegrees))
        - this.eqtimeMins(this.dayOfYear(jsDate))
        ) / 60.0;
        // -ve hours means the previous day, > 24 hrs means the next day
        return this.dateFromHours(hrs,jsDate);
    }

}
/**FIN**/