//--------------------------------------------------------------------------
// JScoord
// jscoord.js
//
// (c) 2005 Jonathan Stott
//
// Created on 21-Dec-2005
//
// 1.1.1 - 16 Jan 2006
//  - Fixed radix bug in getOSRefFromSixFigureReference that would return
//    the incorrect reference if either the northing or the easting started
//    with a leading zero.
// 1.1 - 23 Dec 2005
//  - Added getOSRefFromSixFigureReference function.
// 1.0 - 11 Aug 2005
//  - Initial version created from PHPcoord v1.1
//--------------------------------------------------------------------------


// ================================================================== LatLng

function LatLng(lat, lng) {
  this.lat = lat;
  this.lng = lng;
     
  this.distance = LatLngDistance;
      
  this.toUTMRef = LatLngToUTMRef;
      
  this.toString = LatLngToString;
}

function LatLngToString() {
  return "(" + this.lat + ", " + this.lng + ")";
}

// ================================================================== UTMRef

function UTMRef(easting, northing, latZone, lngZone) {
  this.easting  = easting;
  this.northing = northing;
  this.latZone  = latZone;
  this.lngZone  = lngZone;
    
  this.toLatLng = UTMRefToLatLng;
    
  this.toString = UTMRefToString;
}


function UTMRefToString() {
  return this.lngZone + this.latZone + " " +
         this.easting + " " + this.northing;
}


// ================================================================== RefEll

function RefEll(maj, min) {
  this.maj = maj;
  this.min = min;
  this.ecc = ((maj * maj) - (min * min)) / (maj * maj);
}


// ================================================== Mathematical Functions

function sinSquared(x) {
  return Math.sin(x) * Math.sin(x);
}

function cosSquared(x) {
  return Math.cos(x) * Math.cos(x);
}

function tanSquared(x) {
  return Math.tan(x) * Math.tan(x);
}

function sec(x) {
  return 1.0 / Math.cos(x);
}
  
function deg2rad(x) {
  return x * (Math.PI / 180);
}
  
function rad2deg(x) {
  return x * (180 / Math.PI);
}
  
function chr(x) {
  var h = x.toString (16);
  if (h.length == 1)
    h = "0" + h;
  h = "%" + h;
  return unescape (h);
}
  
function ord(x) {
  var c = x.charAt(0);
  var i;
  for (i = 0; i < 256; ++ i) {
    var h = i.toString (16);
    if (h.length == 1)
      h = "0" + h;
    h = "%" + h;
    h = unescape (h);
    if (h == c)
      break;
  }
  return i;
}


// ========================================================= Other Functions

function LatLngDistance(to) {
  var er = 6366.707;

  var latFrom = deg2rad(this.lat);
  var latTo   = deg2rad(to.lat);
  var lngFrom = deg2rad(this.lng);
  var lngTo   = deg2rad(to.lng);

  var x1 = er * Math.cos(lngFrom) * Math.sin(latFrom);
  var y1 = er * Math.sin(lngFrom) * Math.sin(latFrom);
  var z1 = er * Math.cos(latFrom);

  var x2 = er * Math.cos(lngTo) * Math.sin(latTo);
  var y2 = er * Math.sin(lngTo) * Math.sin(latTo);
  var z2 = er * Math.cos(latTo);

  var d = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));

  return d;
}


// ==================================================== Conversion Functions

/**
 * Convert an UTM reference to a latitude and longitude
 *
 * @param ellipsoid A reference ellipsoid to use
 * @param utm the UTM reference to convert
 * @return the converted latitude and longitude
 * @since 0.2
 */
function UTMRefToLatLng(utmX, utmY, huso_N, huso_L) {
  var wgs84 = new RefEll(6378137, 6356752.314);
  var UTM_F0   = 0.9996;
  var a = wgs84.maj;
  var eSquared = wgs84.ecc;
  var ePrimeSquared = eSquared / (1.0 - eSquared);
  var e1 = (1 - Math.sqrt(1 - eSquared)) / (1 + Math.sqrt(1 - eSquared));
  //var x = this.easting - 500000.0;
  //var y = this.northing;
  var x = ((utmX)?utmX:this.easting) - 500000.0;
  var y = (utmY)?utmY:this.northing;
  //var zoneNumber = this.lngZone;
  //var zoneLetter = this.latZone;
  var zoneNumber = (huso_N)?huso_N:this.lngZone;
  var zoneLetter = (huso_L)?huso_L:this.latZone;

  var longitudeOrigin = (zoneNumber - 1.0) * 6.0 - 180.0 + 3.0;

  // Correct y for southern hemisphere
  if ((ord(zoneLetter) - ord("N")) < 0) {
    y -= 10000000.0;
  }

  var m = y / UTM_F0;
  var mu =
    m
      / (a
        * (1.0
          - eSquared / 4.0
          - 3.0 * eSquared * eSquared / 64.0
          - 5.0
            * Math.pow(eSquared, 3.0)
            / 256.0));

  var phi1Rad =
    mu
      + (3.0 * e1 / 2.0 - 27.0 * Math.pow(e1, 3.0) / 32.0) * Math.sin(2.0 * mu)
      + (21.0 * e1 * e1 / 16.0 - 55.0 * Math.pow(e1, 4.0) / 32.0)
        * Math.sin(4.0 * mu)
      + (151.0 * Math.pow(e1, 3.0) / 96.0) * Math.sin(6.0 * mu);

  var n =
    a
      / Math.sqrt(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
  var t = Math.tan(phi1Rad) * Math.tan(phi1Rad);
  var c = ePrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
  var r =
    a
      * (1.0 - eSquared)
      / Math.pow(
        1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad),
        1.5);
  var d = x / (n * UTM_F0);

  var latitude = (
    phi1Rad
      - (n * Math.tan(phi1Rad) / r)
        * (d * d / 2.0
          - (5.0
            + (3.0 * t)
            + (10.0 * c)
            - (4.0 * c * c)
            - (9.0 * ePrimeSquared))
            * Math.pow(d, 4.0)
            / 24.0
          + (61.0
            + (90.0 * t)
            + (298.0 * c)
            + (45.0 * t * t)
            - (252.0 * ePrimeSquared)
            - (3.0 * c * c))
            * Math.pow(d, 6.0)
            / 720.0)) * (180.0 / Math.PI);

  var longitude = longitudeOrigin + (
    (d
      - (1.0 + 2.0 * t + c) * Math.pow(d, 3.0) / 6.0
      + (5.0
        - (2.0 * c)
        + (28.0 * t)
        - (3.0 * c * c)
        + (8.0 * ePrimeSquared)
        + (24.0 * t * t))
        * Math.pow(d, 5.0)
        / 120.0)
      / Math.cos(phi1Rad)) * (180.0 / Math.PI);

  return new LatLng(latitude, longitude);
}


/**
 * Convert a latitude and longitude to an UTM reference
 *
 * @param ellipsoid A reference ellipsoid to use
 * @param latitudeLongitude The latitude and longitude to convert
 * @return the converted UTM reference
 * @since 0.2
 */
function LatLngToUTMRef() {
  var wgs84 = new RefEll(6378137, 6356752.314);
  var UTM_F0   = 0.9996;
  var a = wgs84.maj;
  var eSquared = wgs84.ecc;
  var longitude = this.lng;
  var latitude = this.lat;

  var latitudeRad = latitude * (Math.PI / 180.0);
  var longitudeRad = longitude * (Math.PI / 180.0);
  var longitudeZone = Math.floor((longitude + 180.0) / 6.0) + 1;

  // Special zone for Norway
  if (latitude >= 56.0
    && latitude < 64.0
    && longitude >= 3.0
    && longitude < 12.0) {
    longitudeZone = 32;
  }

  // Special zones for Svalbard
  if (latitude >= 72.0 && latitude < 84.0) {
    if (longitude >= 0.0 && longitude < 9.0) {
      longitudeZone = 31;
    } else if (longitude >= 9.0 && longitude < 21.0) {
      longitudeZone = 33;
    } else if (longitude >= 21.0 && longitude < 33.0) {
      longitudeZone = 35;
    } else if (longitude >= 33.0 && longitude < 42.0) {
      longitudeZone = 37;
    }
  }
  
  // Special zone for Canary Island
  if (latitude > 22 && latitude < 36 && longitude < -1 && longitude > -30) {
	  longitudeZone = 28;
  }

  var longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
  var longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0);

  var UTMZone = getUTMLatitudeZoneLetter(latitude);

  ePrimeSquared = (eSquared) / (1 - eSquared);

  var n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad));
  var t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
  var c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
  var A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);

  var M =
    a
      * ((1
        - eSquared / 4
        - 3 * eSquared * eSquared / 64
        - 5 * eSquared * eSquared * eSquared / 256)
        * latitudeRad
        - (3 * eSquared / 8
          + 3 * eSquared * eSquared / 32
          + 45 * eSquared * eSquared * eSquared / 1024)
          * Math.sin(2 * latitudeRad)
        + (15 * eSquared * eSquared / 256
          + 45 * eSquared * eSquared * eSquared / 1024)
          * Math.sin(4 * latitudeRad)
        - (35 * eSquared * eSquared * eSquared / 3072)
          * Math.sin(6 * latitudeRad));

  var UTMEasting =
    (UTM_F0
      * n
      * (A
        + (1 - t + c) * Math.pow(A, 3.0) / 6
        + (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared)
          * Math.pow(A, 5.0)
          / 120)
      + 500000.0);

  var UTMNorthing =
    (UTM_F0
      * (M
        + n
          * Math.tan(latitudeRad)
          * (A * A / 2
            + (5 - t + (9 * c) + (4 * c * c)) * Math.pow(A, 4.0) / 24
            + (61 - (58 * t) + (t * t) + (600 * c) - (330 * ePrimeSquared))
              * Math.pow(A, 6.0)
              / 720)));

  // Adjust for the southern hemisphere
  if (latitude < 0) {
    UTMNorthing += 10000000.0;
  }

  return new UTMRef(UTMEasting, UTMNorthing, UTMZone, longitudeZone);
}

/**
 *  Work out the UTM latitude zone from the latitude
 *
 * @param latitude
 * @return
 * @since 0.2
 */
function getUTMLatitudeZoneLetter(latitude) {
  if ((84 >= latitude) && (latitude >= 72)) return "X";
  else if (( 72 > latitude) && (latitude >=  64)) return "W";
  else if (( 64 > latitude) && (latitude >=  56)) return "V";
  else if (( 56 > latitude) && (latitude >=  48)) return "U";
  else if (( 48 > latitude) && (latitude >=  40)) return "T";
  else if (( 40 > latitude) && (latitude >=  32)) return "S";
  else if (( 32 > latitude) && (latitude >=  24)) return "R";
  else if (( 24 > latitude) && (latitude >=  16)) return "Q";
  else if (( 16 > latitude) && (latitude >=   8)) return "P";
  else if ((  8 > latitude) && (latitude >=   0)) return "N";
  else if ((  0 > latitude) && (latitude >=  -8)) return "M";
  else if (( -8 > latitude) && (latitude >= -16)) return "L";
  else if ((-16 > latitude) && (latitude >= -24)) return "K";
  else if ((-24 > latitude) && (latitude >= -32)) return "J";
  else if ((-32 > latitude) && (latitude >= -40)) return "H";
  else if ((-40 > latitude) && (latitude >= -48)) return "G";
  else if ((-48 > latitude) && (latitude >= -56)) return "F";
  else if ((-56 > latitude) && (latitude >= -64)) return "E";
  else if ((-64 > latitude) && (latitude >= -72)) return "D";
  else if ((-72 > latitude) && (latitude >= -80)) return "C";
  else return 'Z';
}

/*
 * Conversión UTM a geográficas
 */
function utm2geo(X, Y, huso)
{
	if (!huso) huso = 28;
	
	var M_PI = 3.1415926536;
	var y,x,phi,nu,a,b,tseta,xi,eta,senh_xi,delta_lambda,t;
	var cos2_phi;
	var B_sub_phi;
	
	var sumando_1, sumando_2, corchete;
	var latitud, longitud;
	var lambda_0;
	
	var hayford = new Object();
	hayford.e2 = 0.0067681703; /* e2 */
	hayford.c = 6399936.609; /* c */
	hayford.a = 6378388.0; /* a */
    //hayford = "Postdam";

	var calcular_B_sub_phi = function(phi) {
		var B;
		var A1,A2,J2,J4,J6,alpha,beta,gamma;
		var cos2_phi = Math.pow(Math.cos(phi), 2.0);
		A1 = Math.sin( 2.0 * phi);
		A2 = A1 * cos2_phi;
		J2 = phi + A1 / 2.0;
		J4 = (3.0 * J2 + A2) / 4.0;
		J6 = (5.0 * J4 + A2 * cos2_phi) / 3.0;
		alpha = 3.0/4.0 * hayford.e2;
		beta = 5.0/3.0 * Math.pow(alpha, 2.0);
		gamma = 35.0/27.0 * Math.pow(alpha, 3.0);
		B = (0.9996 * hayford.c * (phi - alpha * J2 + beta * J4 - gamma * J6));
		return B;
	}
	var meridiano_central_del_huso = function(huso) {
        var lambda;
        lambda = 6.0 * huso - 183.0;
        return lambda;
	}

	y = Y;
	x = X - 500000.0 ;
	phi = y / 6366197.724 / 0.9996 ;
	cos2_phi = Math.pow(Math.cos(phi), 2.0);
	nu = hayford.c * 0.9996 / Math.pow((1.0 + hayford.e2 * cos2_phi), 0.5);
	a = x / nu;
	B_sub_phi = calcular_B_sub_phi(phi);
	b = (y - B_sub_phi) / nu;
	tseta = hayford.e2 * a * a / 2.0 * cos2_phi;
	xi = a * (1.0 - tseta / 3.0);
	eta = b * (1.0 - tseta) + phi;
	senh_xi = (Math.exp(xi) - Math.exp(-xi)) / 2.0;
	delta_lambda = Math.atan2( senh_xi, Math.cos(eta));
	t = Math.atan(Math.cos(delta_lambda) * Math.tan(eta));
	
	sumando_1 = hayford.e2 * cos2_phi;
	sumando_2 = 3.0 / 2.0 * hayford.e2 * Math.sin(phi) * Math.cos(phi) * (t - phi);
	corchete = 1.0 + sumando_1 - sumando_2;
	
	lambda_0 = meridiano_central_del_huso(huso) * M_PI / 180.0;
	
	latitud = phi + corchete * (t-phi); /* phi */
	longitud = delta_lambda + lambda_0; /* lambda */
	
	var ll = [latitud * 180.0 / M_PI, longitud * 180.0 / M_PI];
	return ll;
}