// British National Grid based on code by Bill Chadwick

function BNGGraticule() {
}

BNGGraticule.prototype = new GOverlay();

BNGGraticule.prototype.initialize = function(map) {

  //save for later
  this.map_ = map;

  //array for lines 
  this.lines_ = new Array();
  
  //array for labels
  this.divs_ = new Array();

  this.drawFirst_ = true;
  this.lstnMove_ = null;
  this.lstnStart_ = null;
  this.lstnType_ = null;      

}

BNGGraticule.prototype.remove = function() {

  	this.unDraw();

	//remove handlers we use to trigger redraw / undraw
	if(this.lstnMove_ != null)
		GEvent.removeListener(this.lstnMove_);
	if(this.lstnStart_ != null)
		GEvent.removeListener(this.lstnStart_);
	if(this.lstnType_ != null)
		GEvent.removeListener(this.lstnType_);

}

BNGGraticule.prototype.unDraw = function() {

  try{

	var i = 0;				
	for(i=0; i< this.lines_.length; i++)
		this.map_.removeOverlay(this.lines_[i]);	
			
	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){
  }

}

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

//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
BNGGraticule.prototype.redraw = function(force) {

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

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

  		// We use the type changed event to trigger a redraw too
  		this.lstnType_ = GEvent.addListener(this.map_,"maptypechanged",function(){rdrw ();});

		// 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 graticule based on the current projection and zoom level
BNGGraticule.prototype.safeRedraw = function() {

  //clear old
  this.unDraw();

  //best color for writing on the map
  this.color_ = this.map_.getCurrentMapType().getTextColor();
  if (this.color_ == 'white')
    this.color_ = color["lightGridLines"];
  if (this.color_ == 'black')
    this.color_ = color["darkGridLines"];

  //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 - limit to os grid area
  if (t < 49.0)
	return;
  if(b > 62.0)
	return;
  if(r < -9.5)
    return;  
  if(l > 3.7)
    return;
    

  //grid interval in km   

  var d = 100.0;
  switch (this.map_.getZoom()) // use same interval as Google's scale bar
  {
	case 5:
		d = 100.0;
		break;
	case 6:
		d = 100.0;
		break;
	case 7:
		d = 100.0;
		break;
	case 8:
		d = 100.0;
		break;
	case 9:
		d = 10.0;
		break;
	case 10:
		d = 10.0;
		break;
	case 11:
		d = 10.0;
		break;
	case 12:
		d = 1.0;
		break;
	case 13:
		d = 1.0;
		break;
	case 14:
		d = 1.0;
		break;
	case 15:
		d = 0.1;
		break;
	case 16:
		d = 0.1;
		break;
	case 17:
		d = 0.1;
		break;
	case 18:
		d = 0.01;
		break;
	case 19:
		d = 0.01;
		break;
	case 20:
		d = 0.01;
		break;
	case 21:
		d = 0.001;
		break;
    default:
		return;
  }

  var gr = BNGGraticule_enclosingOsgbRect(l,b,t,r);

  var west = gr.bl.east / 1000.0;
  var south = gr.bl.north / 1000.0;
  var east = gr.tr.east / 1000.0;
  var north = gr.tr.north / 1000.0;
  
  //round iteration limits to the computed grid interval
  east = Math.ceil(east/d)*d;
  west = Math.floor(west/d)*d;
  north = Math.ceil(north/d)*d;
  south = Math.floor(south/d)*d;

  //Sanity / limit
  if (west <= 0.0)
	west = 0.0;
  if(east >= 700.0)
	east = 700.0;
  if(south < 0.0)
    south = 0.0;  
  if(north > 1300.0)
    north = 1300.0;
      
  this.lines_ = new Array();
  this.divs_ = new Array();
  
  var i=0;//count inserted lines
  var j=0;//count labels
  
  //pane/layer to write on
  var mapDiv = this.map_.getPane(G_MAP_MARKER_SHADOW_PANE);
  var thickness = 1;
     
  //horizontal lines
  var s = south;
  while(s<=north){
  
         var pts = new Array();	
         //under 10km grid squares draw as straight line top to bottom	 
         if(d < 10.0){
			pts[0] = this.GLatLngFromEN(east,s);
			pts[1] = this.GLatLngFromEN(west,s);		
		 }
         //over 10km grid squares draw as set of segments
		 else{
			var e = west;
			var q = 0;
			while(e<=east){
				pts[q] = this.GLatLngFromEN(e,s);
			    q++;
				e += d;
			}
		 }

		 //line
		 if(pts.length > 0)
		 {
             if (((Math.round(s*1000)) % (d*10000)) == 0.00){
                 thickness = 2;
             }else{
                 thickness = 1;
             }

		     this.lines_[i] = new GPolyline(pts,this.color_,thickness,1,{clickable:0});
		     this.map_.addOverlay(this.lines_[i]);
		     i++;
		 }
		 
		 s += d; 	
		 
		 	 			 
  }
  

  //vertical lines
  var e = west;
  while(e<=east){

         var pts2 = new Array();		 

         //under 10km grid squares draw as straight line top to bottom	 
         if(d < 10.0){
		 pts2[0] = this.GLatLngFromEN(e,north);
		 pts2[1] = this.GLatLngFromEN(e,south);		
		 }
         //over 10km grid squares draw as set of segments
		 else{
			var s = south;
			var q = 0;
			while(s<=north){
				pts2[q] = this.GLatLngFromEN(e,s);
			    q++;
				s += d;
			}
		 }


		 //line
		 if(pts2.length > 0)
		 {

             if (((Math.round(e*1000)) % (d*10000)) == 0.00){
                 thickness = 2;
             }else{
                 thickness = 1;
             }

		     this.lines_[i] = new GPolyline(pts2,this.color_,thickness,1,{clickable:0});
		     this.map_.addOverlay(this.lines_[i]);
		     i++;
		 }

		 e += d; 		 		
		 
  }
 
}

//from OS east, north in KM to WGS84 Lat/Lon in a GLatLng
BNGGraticule.prototype.GLatLngFromEN = function(eastKm,northKm) {

	var height = 0;

	var lat1 = GT_Math.E_N_to_Lat (eastKm*1000.0,northKm*1000.0,6377563.396,6356256.910,400000,-100000,0.999601272,49.00000,-2.00000);
	var lon1 = GT_Math.E_N_to_Long(eastKm*1000.0,northKm*1000.0,6377563.396,6356256.910,400000,-100000,0.999601272,49.00000,-2.00000);

	var x1 = GT_Math.Lat_Long_H_to_X(lat1,lon1,height,6377563.396,6356256.910);
	var y1 = GT_Math.Lat_Long_H_to_Y(lat1,lon1,height,6377563.396,6356256.910);
	var z1 = GT_Math.Lat_H_to_Z     (lat1,      height,6377563.396,6356256.910);

	var x2 = GT_Math.Helmert_X(x1,y1,z1,446.448 ,0.2470,0.8421,-20.4894);
	var y2 = GT_Math.Helmert_Y(x1,y1,z1,-125.157,0.1502,0.8421,-20.4894);
	var z2 = GT_Math.Helmert_Z(x1,y1,z1,542.060 ,0.1502,0.2470,-20.4894);

	var latitude = GT_Math.XYZ_to_Lat(x2,y2,z2,6378137.000,6356752.313);
	var longitude = GT_Math.XYZ_to_Long(x2,y2);

//		 var ogb = NEtoLL(eastKm*1000.0,northKm*1000.0);
//		 var wg84 = OGBToWGS84(lat,lon,0);


		 return new GLatLng(latitude,longitude);

}

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

	var blOGB = BNGGraticule_WGS84ToOGB(WGbottom,WGleft,0);
	var trOGB = BNGGraticule_WGS84ToOGB(WGtop,WGright,0);
	var brOGB = BNGGraticule_WGS84ToOGB(WGbottom,WGright,0);
	var tlOGB = BNGGraticule_WGS84ToOGB(WGtop,WGleft,0);
	
	var blEN = BNGGraticule_LLtoNE(blOGB.lat,blOGB.lon);
	var trEN = BNGGraticule_LLtoNE(trOGB.lat,trOGB.lon);
	var brEN = BNGGraticule_LLtoNE(brOGB.lat,brOGB.lon);
	var tlEN = BNGGraticule_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 BNGGraticule_OGBRect(new BNGGraticule_OGBNorthEast(e,s),new BNGGraticule_OGBNorthEast(w,n));
}
  
function BNGGraticule_OGBLatLng(lat, lon)
{
	this.lat = lat;
	this.lon = lon;
}

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

function BNGGraticule_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 BNGGraticule_OGBLatLng(newLat, newLon);

}

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

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

//converts lat and lon (OSGB36) to OS northings and eastings
function BNGGraticule_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 = GT_Math.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 BNGGraticule_OGBNorthEast(east, north);
}





