// ================================================================== LatLng
function LatLng(lat, lng) {
  this.lat = lat;
  this.lng = lng;
     
  this.distance = LatLngDistance;
      
  this.toOSRef = LatLngToOSRef;
  this.toUTMRef = LatLngToUTMRef;
      
  this.WGS84ToOSGB36 = WGS84ToOSGB36;
  this.OSGB36ToWGS84 = OSGB36ToWGS84;
      
  this.toString = LatLngToString;
}

function LatLngToString() {
  return "(" + this.lat + ", " + this.lng + ")";
}


// =================================================================== OSRef

// References given with OSRef are accurate to 1m.
function OSRef(easting, northing) {
  this.easting  = easting;
  this.northing = northing;
    
  this.toLatLng = OSRefToLatLng;
    
  this.toString = OSRefToString;
  this.toSixFigureString = OSRefToSixFigureString;
}


function OSRefToString() {
  return "(" + this.easting + ", " + this.northing + ")";
}


function OSRefToSixFigureString() {
  var hundredkmE = Math.floor(this.easting / 100000);
  var hundredkmN = Math.floor(this.northing / 100000);
  var firstLetter = "";
  if (hundredkmN < 5) {
    if (hundredkmE < 5) {
      firstLetter = "S";
    } else {
      firstLetter = "T";
    }
  } else if (hundredkmN < 10) {
    if (hundredkmE < 5) {
      firstLetter = "N";
    } else {
      firstLetter = "O";
    }
  } else {
    firstLetter = "H";
  }

  var secondLetter = "";
  var index = 65 + ((4 - (hundredkmN % 5)) * 5) + (hundredkmE % 5);
  var ti = index;
  if (index >= 73) index++;
  secondLetter = chr(index);

  var e = Math.floor((this.easting - (100000 * hundredkmE)) / 100);
  var n = Math.floor((this.northing - (100000 * hundredkmN)) / 100);
  var es = e;
  if (e < 100) es = "0" + es;
  if (e < 10)  es = "0" + es;
  var ns = n;
  if (n < 100) ns = "0" + ns;
  if (n < 10)  ns = "0" + ns;

  return firstLetter + secondLetter + es + ns;
}


// ================================================================== 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

function OSGB36ToWGS84() {
  var airy1830 = new RefEll(6377563.396, 6356256.909);
  var a        = airy1830.maj;
  var b        = airy1830.min;
  var eSquared = airy1830.ecc;
  var phi = deg2rad(this.lat);
  var lambda = deg2rad(this.lng);
  var v = a / (Math.sqrt(1 - eSquared * sinSquared(phi)));
  var H = 0; // height
  var x = (v + H) * Math.cos(phi) * Math.cos(lambda);
  var y = (v + H) * Math.cos(phi) * Math.sin(lambda);
  var z = ((1 - eSquared) * v + H) * Math.sin(phi);

  var tx =        446.448;
  var ty =       -124.157;
  var tz =        542.060;
  var s  =         -0.0000204894;
  var rx = deg2rad( 0.00004172222);
  var ry = deg2rad( 0.00006861111);
  var rz = deg2rad( 0.00023391666);

  var xB = tx + (x * (1 + s)) + (-rx * y)     + (ry * z);
  var yB = ty + (rz * x)      + (y * (1 + s)) + (-rx * z);
  var zB = tz + (-ry * x)     + (rx * y)      + (z * (1 + s));

  var wgs84 = new RefEll(6378137.000, 6356752.3141);
  a        = wgs84.maj;
  b        = wgs84.min;
  eSquared = wgs84.ecc;

  var lambdaB = rad2deg(Math.atan(yB / xB));
  var p = Math.sqrt((xB * xB) + (yB * yB));
  var phiN = Math.atan(zB / (p * (1 - eSquared)));
  for (var i = 1; i < 10; i++) {
    v = a / (Math.sqrt(1 - eSquared * sinSquared(phiN)));
    phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p);
    phiN = phiN1;
  }

  var phiB = rad2deg(phiN);
    
  this.lat = phiB;
  this.lng = lambdaB;
}

function WGS84ToOSGB36() {
  var wgs84 = new RefEll(6378137.000, 6356752.3141);
  var a        = wgs84.maj;
  var b        = wgs84.min;
  var eSquared = wgs84.ecc;
  var phi = deg2rad(this.lat);
  var lambda = deg2rad(this.lng);
  var v = a / (Math.sqrt(1 - eSquared * sinSquared(phi)));
  var H = 0; // height
  var x = (v + H) * Math.cos(phi) * Math.cos(lambda);
  var y = (v + H) * Math.cos(phi) * Math.sin(lambda);
  var z = ((1 - eSquared) * v + H) * Math.sin(phi);

  var tx =       -446.448;
  var ty =        124.157;
  var tz =       -542.060;
  var s  =          0.0000204894;
  var rx = deg2rad(-0.00004172222);
  var ry = deg2rad(-0.00006861111);
  var rz = deg2rad(-0.00023391666);

  var xB = tx + (x * (1 + s)) + (-rx * y)     + (ry * z);
  var yB = ty + (rz * x)      + (y * (1 + s)) + (-rx * z);
  var zB = tz + (-ry * x)     + (rx * y)      + (z * (1 + s));

  var airy1830 = new RefEll(6377563.396, 6356256.909);
  a        = airy1830.maj;
  b        = airy1830.min;
  eSquared = airy1830.ecc;

  var lambdaB = rad2deg(Math.atan(yB / xB));
  var p = Math.sqrt((xB * xB) + (yB * yB));
  var phiN = Math.atan(zB / (p * (1 - eSquared)));
  for (var i = 1; i < 10; i++) {
    v = a / (Math.sqrt(1 - eSquared * sinSquared(phiN)));
    phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p);
    phiN = phiN1;
  }

  var phiB = rad2deg(phiN);
    
  this.lat = phiB;
  this.lng = lambdaB;
}

function OSRefToLatLng() {
  var airy1830 = new RefEll(6377563.396, 6356256.909);
  var OSGB_F0  = 0.9996012717;
  var N0       = -100000.0;
  var E0       = 400000.0;
  var phi0     = deg2rad(49.0);
  var lambda0  = deg2rad(-2.0);
  var a        = airy1830.maj;
  var b        = airy1830.min;
  var eSquared = airy1830.ecc;
  var phi      = 0.0;
  var lambda   = 0.0;
  var E        = this.easting;
  var N        = this.northing;
  var n        = (a - b) / (a + b);
  var M        = 0.0;
  var phiPrime = ((N - N0) / (a * OSGB_F0)) + phi0;
  do {
    M =
      (b * OSGB_F0)
        * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))
          * (phiPrime - phi0))
          - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
            * Math.sin(phiPrime - phi0)
            * Math.cos(phiPrime + phi0))
          + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
            * Math.sin(2.0 * (phiPrime - phi0))
            * Math.cos(2.0 * (phiPrime + phi0)))
          - (((35.0 / 24.0) * n * n * n)
            * Math.sin(3.0 * (phiPrime - phi0))
            * Math.cos(3.0 * (phiPrime + phi0))));
    phiPrime += (N - N0 - M) / (a * OSGB_F0);
  } while ((N - N0 - M) >= 0.001);
  var v = a * OSGB_F0 * Math.pow(1.0 - eSquared * sinSquared(phiPrime), -0.5);
  var rho =
    a
      * OSGB_F0
      * (1.0 - eSquared)
      * Math.pow(1.0 - eSquared * sinSquared(phiPrime), -1.5);
  var etaSquared = (v / rho) - 1.0;
  var VII = Math.tan(phiPrime) / (2 * rho * v);
  var VIII =
    (Math.tan(phiPrime) / (24.0 * rho * Math.pow(v, 3.0)))
      * (5.0
        + (3.0 * tanSquared(phiPrime))
        + etaSquared
        - (9.0 * tanSquared(phiPrime) * etaSquared));
  var IX =
    (Math.tan(phiPrime) / (720.0 * rho * Math.pow(v, 5.0)))
      * (61.0
        + (90.0 * tanSquared(phiPrime))
        + (45.0 * tanSquared(phiPrime) * tanSquared(phiPrime)));
  var X = sec(phiPrime) / v;
  var XI =
    (sec(phiPrime) / (6.0 * v * v * v))
      * ((v / rho) + (2 * tanSquared(phiPrime)));
  var XII =
    (sec(phiPrime) / (120.0 * Math.pow(v, 5.0)))
      * (5.0
        + (28.0 * tanSquared(phiPrime))
        + (24.0 * tanSquared(phiPrime) * tanSquared(phiPrime)));
  var XIIA =
    (sec(phiPrime) / (5040.0 * Math.pow(v, 7.0)))
      * (61.0
        + (662.0 * tanSquared(phiPrime))
        + (1320.0 * tanSquared(phiPrime) * tanSquared(phiPrime))
        + (720.0
          * tanSquared(phiPrime)
          * tanSquared(phiPrime)
          * tanSquared(phiPrime)));
  phi =
    phiPrime
      - (VII * Math.pow(E - E0, 2.0))
      + (VIII * Math.pow(E - E0, 4.0))
      - (IX * Math.pow(E - E0, 6.0));
  lambda =
    lambda0
      + (X * (E - E0))
      - (XI * Math.pow(E - E0, 3.0))
      + (XII * Math.pow(E - E0, 5.0))
      - (XIIA * Math.pow(E - E0, 7.0));

  return new LatLng(rad2deg(phi), rad2deg(lambda));
}


/**
 * Convert a latitude and longitude into an OSGB grid reference
 *
 * @param latitudeLongitude the latitude and longitude to convert
 * @return the OSGB grid reference
 * @since 0.1
 */
function LatLngToOSRef() {
  var airy1830 = new RefEll(6377563.396, 6356256.909);
  var OSGB_F0  = 0.9996012717;
  var N0       = -100000.0;
  var E0       = 400000.0;
  var phi0     = deg2rad(49.0);
  var lambda0  = deg2rad(-2.0);
  var a        = airy1830.maj;
  var b        = airy1830.min;
  var eSquared = airy1830.ecc;
  var phi = deg2rad(this.lat);
  var lambda = deg2rad(this.lng);
  var E = 0.0;
  var N = 0.0;
  var n = (a - b) / (a + b);
  var v = a * OSGB_F0 * Math.pow(1.0 - eSquared * sinSquared(phi), -0.5);
  var rho =
    a * OSGB_F0 * (1.0 - eSquared) * Math.pow(1.0 - eSquared * sinSquared(phi), -1.5);
  var etaSquared = (v / rho) - 1.0;
  var M =
    (b * OSGB_F0)
      * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))
        * (phi - phi0))
        - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
          * Math.sin(phi - phi0)
          * Math.cos(phi + phi0))
        + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
          * Math.sin(2.0 * (phi - phi0))
          * Math.cos(2.0 * (phi + phi0)))
        - (((35.0 / 24.0) * n * n * n)
          * Math.sin(3.0 * (phi - phi0))
          * Math.cos(3.0 * (phi + phi0))));
  var I = M + N0;
  var II = (v / 2.0) * Math.sin(phi) * Math.cos(phi);
  var III =
    (v / 24.0)
      * Math.sin(phi)
      * Math.pow(Math.cos(phi), 3.0)
      * (5.0 - tanSquared(phi) + (9.0 * etaSquared));
  var IIIA =
    (v / 720.0)
      * Math.sin(phi)
      * Math.pow(Math.cos(phi), 5.0)
      * (61.0 - (58.0 * tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0));
  var IV = v * Math.cos(phi);
  var V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0) * ((v / rho) - tanSquared(phi));
  var VI =
    (v / 120.0)
      * Math.pow(Math.cos(phi), 5.0)
      * (5.0
        - (18.0 * tanSquared(phi))
        + (Math.pow(Math.tan(phi), 4.0))
        + (14 * etaSquared)
        - (58 * tanSquared(phi) * etaSquared));

  N =
    I
      + (II * Math.pow(lambda - lambda0, 2.0))
      + (III * Math.pow(lambda - lambda0, 4.0))
      + (IIIA * Math.pow(lambda - lambda0, 6.0));
  E =
    E0
      + (IV * (lambda - lambda0))
      + (V * Math.pow(lambda - lambda0, 3.0))
      + (VI * Math.pow(lambda - lambda0, 5.0));

  return new OSRef(E, N);
}


/**
 * 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() {
  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 zoneNumber = this.lngZone;
  var zoneLetter = 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;
    }
  }

  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);
}

/**
 * Take a string formatted as a six-figure OS grid reference (e.g.
 * "TG514131") and return a reference to an OSRef object that represents
 * that grid reference. The first character must be H, N, S, O or T.
 * The second character can be any uppercase character from A through Z
 * excluding I.
 *
 * @param ref
 * @return
 * @since 1.1
 */
function getOSRefFromSixFigureReference(ref) {
  var char1 = ref.substring(0, 1);
  var char2 = ref.substring(1, 2);
  // Thanks to Nick Holloway for pointing out the radix bug here
  var east  = parseInt(ref.substring(2, 5), 10) * 100;
  var north = parseInt(ref.substring(5, 8), 10) * 100;
  if (char1 == 'H') {
    north += 1000000;
  } else if (char1 == 'N') {
    north += 500000;
  } else if (char1 == 'O') {
    north += 500000;
    east  += 500000;
  } else if (char1 == 'T') {
    east += 500000;
  }
  var char2ord = ord(char2);
  if (char2ord > 73) char2ord--; // Adjust for no I
  var nx = ((char2ord - 65) % 5) * 100000;
  var ny = (4 - Math.floor((char2ord - 65) / 5)) * 100000;
  return new OSRef(east + nx, north + ny);
}


/**
 *  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';
}