

function GPSCoordinateSet(coordStr)
{
   if (altitude == null)
	altitude = 0;

   var lat = "";
   var lon = "";
   var sortKey = 0;
   var geolog;
   var altitude;   
   var rectX;
   var rectY;
   var rectZ;
   var hemi;
   var zone;
   var UTMx;
   var UTMy;

   // parse out each part
   this.altitude = altitude;

   var coordStr =  coordStr.replace("\r\n",  ' ');
   coordStr =  coordStr.replace("\n",  ' ');
   coordStr =  coordStr.replace("\r",  ' ');
   coordStr =  coordStr.replace("&deg;",  '');

//	alert("\"coordStr\"");

   var matches = new Array();


// GPS coordinates
   matches = coordStr.match(/([NWSE\-]?)([^0-9NWSE]{0,3}[0-9]{1,3}[^0-9]{1,4}[0-9]{1,2}[^0-9]{1,3}[0-9]{1,3})[^a-z0-9\-]*([NWSE\-]?)([^0-9NWSE]{0,3}[0-9]{1,3}[^0-9]{1,3}[0-9]{1,2}[^0-9]{1,3}[0-9]{1,3})[^NWSE]*([NWSE]?)/im);

   if (!matches)
   {
//    alert("did not match GPS regex<BR>\n");

      // decimal coordinates
      matches = coordStr.match(/([NWSE\-]?[^0-9]*[0-9]{1,3}\.[0-9]{2,}[^a-z0-9\-]+)([NWSE\-]?[^0-9]*[0-9]{1,3}\.[0-9]{2,})/i);

      lat = matches[1];
      lon = matches[2];

//      alert(lat + " " + lon);
	
   }
   else
   {
      latDir1 = matches[1];
      latDir2 = matches[3];
      lonDir1 = matches[3];
      lonDir2 = matches[5];
      lat = matches[2];
      lon = matches[4];	

//		var_dump(matches);


	  // we either use "-", "" or NWSE, can't use both
 	  if (latDir2.match("/\-/") && latdir1.match("/[NWSE]/i"))
	{
			latDir1 = "";
		}

		// we want 1 and 1 or 2 and 2 or blank and 1 or 1 and blank
		if (latDir1 != "" && lonDir1 != "")
		{
		  lat = latDir1 + lat;
		  lon = lonDir1 + lon;
		}
		else if (latDir2 != "" && lonDir2 != "")
		{
		  lat = latDir2 + lat;
		  lon = lonDir2 + lon;
		}
		else if (latDir1 == "" && lonDir1 != "")
		{
		  lat = "N" + lat;
		  lon = lonDir1 + lon;
		}
		else if (latDir1 != "" && lonDir1 == "")
		{
		  lat = latDir1 + lat;
		  lon = "E" + lon;
		}
		else
		{
		  lat = "N" + lat;
		  lon = "E" + lon;
		}

	}

       this.lat = new GPSCoordinate(lat, "lat");
       this.lon = new GPSCoordinate(lon, "lon");

//       alert("coords " + this.lat.toDec() + " " + this.lon.toDec() + "<BR>\n");
   
       var UTMArr = dec2utm(this.lat.toDec(), this.lon.toDec());

//       alert(UTMArr);

       this.hemi  = UTMArr[0];
       this.zone  = UTMArr[1];
       this.UTMx  = UTMArr[2];
       this.UTMy  = UTMArr[3];

       this.calcRect();
   }

function deg2rad(deg)
{
    return (deg / 180.0 * Math.PI)
}

GPSCoordinateSet.prototype.calcRect = GPSCoordinateSet_calcRect;
function GPSCoordinateSet_calcRect()
{
//   alert("Lat: " + (this.lat.toDecString()));
       latdec = deg2rad(this.lat.toDecString());
       londec = deg2rad(this.lon.toDecString());
//   alert("dec: latdec londec\n");
    
   er = 6378137.0;
   f = 1.0/298.257223560;
   ee =  2.0 * f - f * f;
   h = this.altitude;
//   alert("Alt: h\n");

   b = er / Math.sqrt(1 - ee * Math.sin(latdec) * Math.sin(latdec));
   d = (b + h) * Math.cos(latdec);
   this.rectX = d * Math.cos(londec);
   this.rectY = d * Math.sin(londec);
   this.rectZ = (b *(1 - ee) + h) * Math.sin(latdec);
}

// -------------------------------------------------------------------------
// METHOD:  CLatLon::VincentyDistance()
/*! 
   \brief  Calculates the distance and forward and reverse azimuths between 
           this point and P using the Vincenty method.

   \author fizzymagic
   \date   9/6/2003

   \return  [double] - Distance between this point and P in meters.

   \param P [CLatLon&] - Point to which to compute distance.
   \param pForwardAzimuth [double *] - Pointer to parameter to receive 
                                       forward azimuth in degrees.
   \param pReverseAzimuth [double *] - Pointer to parameter to receive
                                       reverse azimuth in degrees.


   This method computes a high-accuracy distance between this point and P
   using the Vincenty method and the WGS84 ellipsoid.  It also calculates 
   and returns the forward and reverse azimuths.
*/
// -------------------------------------------------------------------------

GPSCoordinateSet.prototype.vincentyDistance = GPSCoordinateSet_vincentyDistance;
function GPSCoordinateSet_vincentyDistance(cset, units)
{
  if (units == null) units = "m";

  return this.distance(cset, units);
}

GPSCoordinateSet.prototype.distance = GPSCoordinateSet_distance;
function GPSCoordinateSet_distance(cset, units)
{
   if (units == null) units = "m";

   result['forward'] = 0;
   result['reverse'] = 0;
   result['distance'] = 0;

   if (cset.toGPSString() == this.toGPSString())
   {
	return result;
   }

// Degrees to radians conversion.
   Deg2Rad = 1.74532925199433E-02;

   EPSILON = 5.e-14;

   m_ellipsoidA = 6378137.00;
   m_ellipsoidInv = 298.257223563;

   // Check to see if either latitude is 90 degrees exactly.
   // In that case, the distance is a closed-form expression!
   
   thislat = this.getLat();
   thislon = this.getLong();

   plat = cset.getLat();
   plon = cset.getLong();
   dLat1  = Deg2Rad * thislat.toDec();
   dLat2  = Deg2Rad * plat.toDec();
   dLong1 = Deg2Rad * thislon.toDec();
   dLong2 = Deg2Rad * plon.toDec();

/*
   var_dump(thislat.toDec());
   var_dump(thislon.toDec());
   var_dump(plat.toDec());
   var_dump(plon.toDec());
*/

   a0 = m_ellipsoidA;
   flat = 1.0 / m_ellipsoidInv;
   r = 1.0 - flat;
   b0 = a0 * r;

   tanu1 = r * tan(dLat1);
   tanu2 = r * tan(dLat2);

   dtmp = Math.atan(tanu1);

   if (Math.abs(thislat.toDec()) >= 90.0)
   {
	dtmp = dLat1;
   }

   cosu1 = Math.cos(dtmp);
   sinu1 = Math.sin(dtmp);

   dtmp = Math.atan(tanu2);
   if (Math.abs(plat.toDec()) >= 90.0) 
   {
      dtmp = dLat2;
   }

   cosu2 = Math.cos(dtmp);
   sinu2 = Math.sin(dtmp);

   omega = dLong2 - dLong1;
   lambda = omega;

   do {
      testlambda = lambda;
      ss1 = cosu2 * Math.sin(lambda);
      ss2 = cosu1 * sinu2 - sinu1 * cosu2 * Math.cos(lambda);
      ss = Math.sqrt(ss1 * ss1 + ss2 * ss2);
      cs = sinu1 * sinu2 + cosu1 * cosu2 * Math.cos(lambda);
      tansigma = ss / cs;
      sinalpha = cosu1 * cosu2 * Math.sin(lambda) / ss;
      dtmp = Math.asin(sinalpha);
      cosalpha = Math.cos(dtmp);
      cosalpha2 = cosalpha * cosalpha; 
      c2sm = cs - 2.0*sinu1*sinu2/cosalpha2;
      c = flat/16.0 * cosalpha2*(4.0 + flat*(4.0 - 3.0*cosalpha2));
      lambda = omega + (1.0 - c)*flat*sinalpha*(Math.asin(ss) + c*ss*(c2sm + c*cs*(-1.0 + 2.0*c2sm*c2sm)));
      dDeltaLambda = Math.abs(testlambda - lambda);
   } while (dDeltaLambda > EPSILON);

   u2 = cosalpha2 * (a0*a0 - b0*b0)/(b0*b0);
   a = 1.0 + (u2 / 16384.0) * (4096.0 + u2 * (-768.0 + u2 * (320.0 - 175.0 * u2)));
   b = (u2 / 1024.0) * (256.0 + u2 * (-128.0 + u2 * (74.0 - 47.0 * u2)));

   dsigma = b * ss * (c2sm + (b / 4.0) * (cs * (-1.0 + 2.0 * c2sm*c2sm) 
                 - (b / 6.0) * c2sm * (-3.0 + 4.0 * ss*ss) * (-3.0 + 4.0 * c2sm*c2sm)));

   s = b0 * a * (Math.asin(ss) - dsigma);

   alpha12 = Math.atan2(cosu2 * Math.sin(lambda), (cosu1 * sinu2 - sinu1 * cosu2 * Math.cos(lambda)))/Deg2Rad;
   alpha21 = Math.atan2(cosu1 * Math.sin(lambda), (-sinu1 * cosu2 + cosu1 * sinu2 * Math.cos(lambda)))/Deg2Rad;

   result['distance'] = this.convertUnits(s, units);

   result['forward'] = (alpha12 + 360.0) % 360.0;
   result['reverse'] = (alpha21 + 180.0) % 360.0;

   return result;
}

GPSCoordinateSet.prototype.convertUnits = GPSCoordinateSet_convertUnits;
function GPSCoordinateSet_convertUnits(s, units)
{

   units = units.toUpperCase();

   if (units == "K")
	return s / 1000.00;
   else if (units == "NM")
	return s / 1852.0; 
   else 
	return s / 1609.344; 
}

GPSCoordinateSet.prototype.distance3D = GPSCoordinateSet_distance3D;
   function GPSCoordinateSet_distance3D(cset, units)
   {
      if (units == null) units = "m";

	xd = cset.getRectX() - this.getRectX();
	yd = cset.getRectY() - this.getRectY();
	zd = cset.getRectZ() - this.getRectZ();

	return this.convertUnits(Math.sqrt(xd*xd + yd*yd + zd*zd), units);
   }

GPSCoordinateSet.prototype.toRectString = GPSCoordinateSet_toRectString;
   function GPSCoordinateSet_toRectString()
   {
      return this.rectX + ", " + this.rectY + ", " + this.rectZ;
   }

GPSCoordinateSet.prototype.getRectX = GPSCoordinateSet_getRectX;
   function GPSCoordinateSet_getRectX()
   {
      return this.rectX;
   }
GPSCoordinateSet.prototype.getRectY = GPSCoordinateSet_getRectY;
   function GPSCoordinateSet_getRectY()
   {
      return this.rectY;
   }
GPSCoordinateSet.prototype.getRectZ = GPSCoordinateSet_getRectZ;
   function GPSCoordinateSet_getRectZ()
   {
      return this.rectZ;
   }

GPSCoordinateSet.prototype.gcDistance = GPSCoordinateSet_gcDistance;
   function GPSCoordinateSet_gcDistance(cset, units)
   {
//	alert("calculating distance from " + gettype(cset) + " to this<BR>");

	lt1 = cset.getLat();
	ln1 = cset.getLong();
	
	lon1 = ln1.toDecString();
	lat1 = lt1.toDecString();

	lt2 = this.lat;
	ln2 = this.lon;

	lon2 = ln2.toDecString();
	lat2 = lt2.toDecString();

//	alert("calculating distance from lat1, lon1 to lat2, lon2<BR>");

	theta = lon1 - lon2; 
 	dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) +Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta)); 
 	dist = Math.acos(dist); 
 	dist = rad2deg(dist); 
 	miles = dist * 60 * 1.1515; 
	unit = unit.toUpperCase(); 

        if (unit == "K") { 
        	return (miles * 1.609344); 
 	} else if (unit == "N") { 
        	return (miles * 0.8684); 
	} else { 
        	return miles; 
       	} 
   }

GPSCoordinateSet.prototype.compareBySortKey = GPSCoordinateSet_compareBySortKey;
   function GPSCoordinateSet_compareBySortKey(a, b)
   { 
//	alert("comparing " + a.getSortKey() + " " + b.getSortKey() + "<BR>");

        if (a.getSortKey() == b.getSortKey()) {
          return 0;
        }

        return (a.getSortKey() < b.getSortKey()) ? -1 : 1;
   }

GPSCoordinateSet.prototype.getLat = GPSCoordinateSet_getLat;
   function GPSCoordinateSet_getLat()
   {
	return this.lat;
   }

GPSCoordinateSet.prototype.getZone = GPSCoordinateSet_getZone;
   function GPSCoordinateSet_getZone()
   {
	return this.zone;
   }

GPSCoordinateSet.prototype.getUTMx = GPSCoordinateSet_getUTMx;
   function GPSCoordinateSet_getUTMx()
   {
	return this.UTMx;
   }

GPSCoordinateSet.prototype.getUTMy = GPSCoordinateSet_getUTMy;
   function GPSCoordinateSet_getUTMy()
   {
	return this.UTMy;
   }

GPSCoordinateSet.prototype.getHemi = GPSCoordinateSet_getHemi;
   function GPSCoordinateSet_getHemi()
   {
	return this.hemi;
   }

GPSCoordinateSet.prototype.getSortKey = GPSCoordinateSet_getSortKey;
   function GPSCoordinateSet_getSortKey()
   {
	return this.sortKey;
   }

GPSCoordinateSet.prototype.setSortKey = GPSCoordinateSet_setSortKey;
   function GPSCoordinateSet_setSortKey(key)
   {
	return this.sortKey = key;
   }

GPSCoordinateSet.prototype.addGPS = GPSCoordinateSet_addGPS;
   function GPSCoordinateSet_addGPS(latDeg, latMin, lonDeg, lonMin)
   {
	this.lat.addGPS(latDeg, latMin);
	this.lon.addGPS(lonDeg, lonMin);
        this.calcRect();
   }

GPSCoordinateSet.prototype.getLong = GPSCoordinateSet_getLong;
   function GPSCoordinateSet_getLong()
   {
	return this.lon;
   }

GPSCoordinateSet.prototype.isValid = GPSCoordinateSet_isValid;
   function GPSCoordinateSet_isValid()
   {
	return ((this.lat.toDec() + this.lon.toDec()) != 0);
   }

  GPSCoordinateSet.prototype.toGPSString = GPSCoordinateSet_toGPSString;

   function GPSCoordinateSet_toGPSString()
   {
	return this.lat.toGPSString() + "  " + this.lon.toGPSString();
   }

GPSCoordinateSet.prototype.toDecString = GPSCoordinateSet_toDecString;
   function GPSCoordinateSet_toDecString()
   {
	return this.lat.toDecString() + "  " + this.lon.toDecString();

   }


   GPSCoordinateSet.prototype.toUTMString = GPSCoordinateSet_toUTMString;
   function GPSCoordinateSet_toUTMString()
   {
	return this.hemi + this.zone + " " + Math.round(this.UTMx) + " " + Math.round(this.UTMy);
   }


function GPSCoordinate(coords, type)
{
	if (type == null)
	  type = "lat";

	this.type = type;
	this.dir = "";
	this.deg = "";
	this.min = "";
	this.sec = "";
	this.wholeSec = "";
	this.dec = 0;
	this.sign = "";
	this.cset = new Array();

	this.parse(coords, type);
}


GPSCoordinate.prototype.parse = GPSCoordinate_parse;

function GPSCoordinate_parse(coords, type) 
{
        this.cset['unparsed'] = coords;     
	this.type = type;
	this.sign = "";

//	alert("matching '" + coords + "'\n");

	var matches = new Array();

	// "sa(N38.8600667,W9.2310667"
	if (matches = coords.match(/([NWSE\-])?[^0-9NWSE\-]{0,3}0*([0-9]{1,3})[^0-9]{1,3}([0-9]{1,2})([^0-9]{1,3})([0-9]{1,3})([NWSE])?/i))
        {
	    // GPS Coordinates
//	    alert("GPS match in GPSCoordinate coords " + matches);

//	    alert(parseInt(matches[2]));

	    this.dir = matches[1];
	    this.deg = parseInt(matches[2], 10);
	    this.min = parseInt(matches[3], 10);

	    if (matches[1] == "" && matches[2] != "")
	    {
	      this.dir = matches[2];
	    }

	    if (matches[5].length < 3 && parseInt(matches[5]) < 60 && matches[4].indexOf('.') < 0)
	    {
//	       alert("using dms");
	       // we probably have DMS coords
	       this.sec = parseInt(matches[5], 10) / 60.0;
	    }
	    else
            {
  //	    # can be 600, 60 or 6
	      this.sec = parseFloat("." + matches[5]);
	    }

            this.dec = (this.min + this.sec)/60.0;

//	    alert("dir = " + this.dir + ", deg = " + this.deg + ", min = " + this.min + ", sec = " + this.sec);

	}
	else
	{
//	    alert("dec match in GPSCoordinate coords<BR>\n");

	    // Decimal coordinates

            matches = coords.match(/([NWSE\-\+])?[^0-9NWSE\-\+]*(([0-9]{1,3})\.([0-9]{2,}))/i);

            // 0 -71.46755
            // 1 -
 	    // 2 71.46755
	    // 3 71
	    // 4 46755

//	    alert("matches: \'" +  matches.join( "', '") + "\'");
	
	    // 1 = sign, 2 = deg, 3 = dec

	    this.dir = matches[1];
	    this.deg = parseInt(matches[3], 10);
//	    alert(this.deg);
            this.dec = parseFloat("0." + matches[4]);

	    this.min = (matches[2] - matches[3]) * 60.0;
	    this.sec = (this.min - Math.floor(this.min));
	    this.min = Math.floor(this.min);

        }

	// common parsing

        this.dir = String("" + this.dir).toUpperCase().trim();

	if (this.dir == "S" || this.dir == "W")
	{
	  this.sign = "-";
	}
	else if (this.dir == "-")
        {
           this.sign = "-";
	   if (type == "lat")
           {
	      this.dir = "S";
	   }
	   else
           {
	      this.dir = "W";
	   }
        }
	else
        {
	   this.dir = "";
	   if (type == "lat")
           {
	      this.dir = "N";
	   }
	   else
           {
	      this.dir = "E";
	   }
	}

	this.wholeSec = Math.floor((this.sec * 60.0));

   }	


   // TODO: this doesn't handle going from W to E or N to S

GPSCoordinate.prototype.addGPS = GPSCoordinate_addGPS;

   function GPSCoordinate_addGPS(deg, min)
   {
	deg = this.deg + deg;
	
	newmin = this.min + this.sec + min;
	if (newmin > 60)
	{
	  deg++;
	  newmin -= 60;
	}
	else if (newmin < 0)
	{
	  deg--;
	  newmin += 60;
	}
 	
	newstr = sprintf("%s %d %02.3f ", this.dir, deg, newmin);
//	alert("parsing newstr\n");
	this.parse(newstr, this.type);
   }

GPSCoordinate.prototype.toGPSString = GPSCoordinate_toGPSString;

function GPSCoordinate_toGPSString()
{
  return this.dir + " " + this.deg + " " + lz((this.min + this.sec).toFixed(3), 6);
}

function GPSCoordinate_toDec()
{
 return parseFloat(this.sign + (this.deg + this.dec));
}
GPSCoordinate.prototype.toDec = GPSCoordinate_toDec;

function GPSCoordinate_toDecString()
{
	return this.sign + (this.deg + this.dec);
}
GPSCoordinate.prototype.toDecString = GPSCoordinate_toDecString;

function GPSCoordinate_toDMSString()
{
	return sprintf("%s%02d %s %s", this.dir, this.deg, lz(this.min,2), lz(this.wholeSec,2));
}
GPSCoordinate.prototype.toDMSString = GPSCoordinate_toDMSString;


String.prototype.trim = function()
{
    // Use a regular expression to replace leading and trailing 
    // spaces with the empty string
    return this.replace(/(^\s*)|(\s*$)/g, "");
}

function sprintf()
{
	if (!arguments || arguments.length < 1 || !RegExp)
	{
		return;
	}
	var str = arguments[0];
	var re = /([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/;
	var a = b = [], numSubstitutions = 0, numMatches = 0;
	while (a = re.exec(str))
	{
   	var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4];
	var pPrecision = a[5], pType = a[6], rightPart = a[7];
				
	//alert(a + '\n' + [a[0], leftpart, pPad, pJustify, pMinLength, pPrecision);

		numMatches++;
		if (pType == '%')
		{
			subst = '%';
		}
		else
		{
					numSubstitutions++;
					if (numSubstitutions >= arguments.length)
					{
						alert('Error! Not enough function arguments (' + (arguments.length - 1) + ', excluding the string)\nfor the number of substitution parameters in string (' + numSubstitutions + ' so far).');
					}
					var param = arguments[numSubstitutions];
					var pad = '';
					       if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1);
					  else if (pPad) pad = pPad;
					var justifyRight = true;
					       if (pJustify && pJustify === "-") justifyRight = false;
					var minLength = -1;
					       if (pMinLength) minLength = parseInt(pMinLength);
					var precision = -1;
					       if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1));
					var subst = param;
					       if (pType == 'b') subst = parseInt(param).toString(2);
					  else if (pType == 'c') subst = String.fromCharCode(parseInt(param));
					  else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0;
					  else if (pType == 'u') subst = Math.abs(param);
					  else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
					  else if (pType == 'o') subst = parseInt(param).toString(8);
					  else if (pType == 's') subst = param;
					  else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase();
					  else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase();
				}
				str = leftpart + subst + rightPart;
			}
			return str;
		}
		

 function Stretch(Q, L, c) { var S = Q
 if (c.length>0) while (S.length<L) { S = c+S }
 return S
}
 function StrU(X, M, N) { // X>=0.0
 var T, S=new String(Math.round(X*Number("1e"+N)))
 if (S.search && S.search(/\D/)!=-1) { return ''+X }
 with (new String(Stretch(S, M+N, '0')))
 return substring(0, T=(length-N)) + '.' + substring(T)
}
 function Sign(X) { return X<0 ? '-' : ''; }
function StrS(X, M, N) { return Sign(X)+StrU(Math.abs(X), M, N) }
 Number.prototype.toFixed= new Function('n','return StrS(this,1,n)')



    var pi = Math.PI;

    /* Ellipsoid model constants (actual values here are for WGS84) */
    var sm_a = 6378137.0;
    var sm_b = 6356752.314;
    var sm_EccSquared = 6.69437999013e-03;

    var UTMScaleFactor = 0.9996;


    /*
    * DegToRad
    *
    * Converts degrees to radians.
    *
    */
    function DegToRad (deg)
    {
        return (deg / 180.0 * pi)
    }




    /*
    * RadToDeg
    *
    * Converts radians to degrees.
    *
    */
    function RadToDeg (rad)
    {
        return (rad / pi * 180.0)
    }




    /*
    * ArcLengthOfMeridian
    *
    * Computes the ellipsoidal distance from the equator to a point at a
    * given latitude.
    *
    * Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
    * GPS: Theory and Practice, 3rd ed.  New York: Springer-Verlag Wien, 1994.
    *
    * Inputs:
    *     phi - Latitude of the point, in radians.
    *
    * Globals:
    *     sm_a - Ellipsoid model major axis.
    *     sm_b - Ellipsoid model minor axis.
    *
    * Returns:
    *     The ellipsoidal distance of the point from the equator, in meters.
    *
    */
    function ArcLengthOfMeridian (phi)
    {
        var alpha, beta, gamma, delta, epsilon, n;
        var result;

        /* Precalculate n */
        n = (sm_a - sm_b) / (sm_a + sm_b);

        /* Precalculate alpha */
        alpha = ((sm_a + sm_b) / 2.0)
           * (1.0 + (Math.pow (n, 2.0) / 4.0) + (Math.pow (n, 4.0) / 64.0));

        /* Precalculate beta */
        beta = (-3.0 * n / 2.0) + (9.0 * Math.pow (n, 3.0) / 16.0)
           + (-3.0 * Math.pow (n, 5.0) / 32.0);

        /* Precalculate gamma */
        gamma = (15.0 * Math.pow (n, 2.0) / 16.0)
            + (-15.0 * Math.pow (n, 4.0) / 32.0);
    
        /* Precalculate delta */
        delta = (-35.0 * Math.pow (n, 3.0) / 48.0)
            + (105.0 * Math.pow (n, 5.0) / 256.0);
    
        /* Precalculate epsilon */
        epsilon = (315.0 * Math.pow (n, 4.0) / 512.0);
    
    /* Now calculate the sum of the series and return */
    result = alpha
        * (phi + (beta * Math.sin (2.0 * phi))
            + (gamma * Math.sin (4.0 * phi))
            + (delta * Math.sin (6.0 * phi))
            + (epsilon * Math.sin (8.0 * phi)));

    return result;
    }



    /*
    * UTMCentralMeridian
    *
    * Determines the central meridian for the given UTM zone.
    *
    * Inputs:
    *     zone - An integer value designating the UTM zone, range [1,60].
    *
    * Returns:
    *   The central meridian for the given UTM zone, in radians, or zero
    *   if the UTM zone parameter is outside the range [1,60].
    *   Range of the central meridian is the radian equivalent of [-177,+177].
    *
    */
    function UTMCentralMeridian (zone)
    {
        var cmeridian;

        cmeridian = DegToRad (-183.0 + (zone * 6.0));
    
        return cmeridian;
    }



    /*
    * FootpointLatitude
    *
    * Computes the footpoint latitude for use in converting transverse
    * Mercator coordinates to ellipsoidal coordinates.
    *
    * Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
    *   GPS: Theory and Practice, 3rd ed.  New York: Springer-Verlag Wien, 1994.
    *
    * Inputs:
    *   y - The UTM northing coordinate, in meters.
    *
    * Returns:
    *   The footpoint latitude, in radians.
    *
    */
    function FootpointLatitude (y)
    {
        var y_, alpha_, beta_, gamma_, delta_, epsilon_, n;
        var result;
        
        /* Precalculate n (Eq. 10.18) */
        n = (sm_a - sm_b) / (sm_a + sm_b);
        	
        /* Precalculate alpha_ (Eq. 10.22) */
        /* (Same as alpha in Eq. 10.17) */
        alpha_ = ((sm_a + sm_b) / 2.0)
            * (1 + (Math.pow (n, 2.0) / 4) + (Math.pow (n, 4.0) / 64));
        
        /* Precalculate y_ (Eq. 10.23) */
        y_ = y / alpha_;
        
        /* Precalculate beta_ (Eq. 10.22) */
        beta_ = (3.0 * n / 2.0) + (-27.0 * Math.pow (n, 3.0) / 32.0)
            + (269.0 * Math.pow (n, 5.0) / 512.0);
        
        /* Precalculate gamma_ (Eq. 10.22) */
        gamma_ = (21.0 * Math.pow (n, 2.0) / 16.0)
            + (-55.0 * Math.pow (n, 4.0) / 32.0);
        	
        /* Precalculate delta_ (Eq. 10.22) */
        delta_ = (151.0 * Math.pow (n, 3.0) / 96.0)
            + (-417.0 * Math.pow (n, 5.0) / 128.0);
        	
        /* Precalculate epsilon_ (Eq. 10.22) */
        epsilon_ = (1097.0 * Math.pow (n, 4.0) / 512.0);
        	
        /* Now calculate the sum of the series (Eq. 10.21) */
        result = y_ + (beta_ * Math.sin (2.0 * y_))
            + (gamma_ * Math.sin (4.0 * y_))
            + (delta_ * Math.sin (6.0 * y_))
            + (epsilon_ * Math.sin (8.0 * y_));
        
        return result;
    }



    /*
    * MapLatLonToXY
    *
    * Converts a latitude/longitude pair to x and y coordinates in the
    * Transverse Mercator projection.  Note that Transverse Mercator is not
    * the same as UTM; a scale factor is required to convert between them.
    *
    * Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
    * GPS: Theory and Practice, 3rd ed.  New York: Springer-Verlag Wien, 1994.
    *
    * Inputs:
    *    phi - Latitude of the point, in radians.
    *    lambda - Longitude of the point, in radians.
    *    lambda0 - Longitude of the central meridian to be used, in radians.
    *
    * Outputs:
    *    xy - A 2-element array containing the x and y coordinates
    *         of the computed point.
    *
    * Returns:
    *    The function does not return a value.
    *
    */
    function MapLatLonToXY (phi, lambda, lambda0, xy)
    {
        var N, nu2, ep2, t, t2, l;
        var l3coef, l4coef, l5coef, l6coef, l7coef, l8coef;
        var tmp;

        /* Precalculate ep2 */
        ep2 = (Math.pow (sm_a, 2.0) - Math.pow (sm_b, 2.0)) / Math.pow (sm_b, 2.0);
    
        /* Precalculate nu2 */
        nu2 = ep2 * Math.pow (Math.cos (phi), 2.0);
    
        /* Precalculate N */
        N = Math.pow (sm_a, 2.0) / (sm_b * Math.sqrt (1 + nu2));
    
        /* Precalculate t */
        t = Math.tan (phi);
        t2 = t * t;
        tmp = (t2 * t2 * t2) - Math.pow (t, 6.0);

        /* Precalculate l */
        l = lambda - lambda0;
    
        /* Precalculate coefficients for l**n in the equations below
           so a normal human being can read the expressions for easting
           and northing
           -- l**1 and l**2 have coefficients of 1.0 */
        l3coef = 1.0 - t2 + nu2;
    
        l4coef = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2);
    
        l5coef = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2
            - 58.0 * t2 * nu2;
    
        l6coef = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2
            - 330.0 * t2 * nu2;
    
        l7coef = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2);
    
        l8coef = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2);
    
        /* Calculate easting (x) */
        xy[0] = N * Math.cos (phi) * l
            + (N / 6.0 * Math.pow (Math.cos (phi), 3.0) * l3coef * Math.pow (l, 3.0))
            + (N / 120.0 * Math.pow (Math.cos (phi), 5.0) * l5coef * Math.pow (l, 5.0))
            + (N / 5040.0 * Math.pow (Math.cos (phi), 7.0) * l7coef * Math.pow (l, 7.0));
    
        /* Calculate northing (y) */
        xy[1] = ArcLengthOfMeridian (phi)
            + (t / 2.0 * N * Math.pow (Math.cos (phi), 2.0) * Math.pow (l, 2.0))
            + (t / 24.0 * N * Math.pow (Math.cos (phi), 4.0) * l4coef * Math.pow (l, 4.0))
            + (t / 720.0 * N * Math.pow (Math.cos (phi), 6.0) * l6coef * Math.pow (l, 6.0))
            + (t / 40320.0 * N * Math.pow (Math.cos (phi), 8.0) * l8coef * Math.pow (l, 8.0));
    
        return;
    }
    
    
    
    /*
    * MapXYToLatLon
    *
    * Converts x and y coordinates in the Transverse Mercator projection to
    * a latitude/longitude pair.  Note that Transverse Mercator is not
    * the same as UTM; a scale factor is required to convert between them.
    *
    * Reference: Hoffmann-Wellenhof, B., Lichtenegger, H., and Collins, J.,
    *   GPS: Theory and Practice, 3rd ed.  New York: Springer-Verlag Wien, 1994.
    *
    * Inputs:
    *   x - The easting of the point, in meters.
    *   y - The northing of the point, in meters.
    *   lambda0 - Longitude of the central meridian to be used, in radians.
    *
    * Outputs:
    *   philambda - A 2-element containing the latitude and longitude
    *               in radians.
    *
    * Returns:
    *   The function does not return a value.
    *
    * Remarks:
    *   The local variables Nf, nuf2, tf, and tf2 serve the same purpose as
    *   N, nu2, t, and t2 in MapLatLonToXY, but they are computed with respect
    *   to the footpoint latitude phif.
    *
    *   x1frac, x2frac, x2poly, x3poly, etc. are to enhance readability and
    *   to optimize computations.
    *
    */
    function MapXYToLatLon (x, y, lambda0, philambda)
    {
        var phif, Nf, Nfpow, nuf2, ep2, tf, tf2, tf4, cf;
        var x1frac, x2frac, x3frac, x4frac, x5frac, x6frac, x7frac, x8frac;
        var x2poly, x3poly, x4poly, x5poly, x6poly, x7poly, x8poly;
    	
        /* Get the value of phif, the footpoint latitude. */
        phif = FootpointLatitude (y);
        	
        /* Precalculate ep2 */
        ep2 = (Math.pow (sm_a, 2.0) - Math.pow (sm_b, 2.0))
              / Math.pow (sm_b, 2.0);
        	
        /* Precalculate cos (phif) */
        cf = Math.cos (phif);
        	
        /* Precalculate nuf2 */
        nuf2 = ep2 * Math.pow (cf, 2.0);
        	
        /* Precalculate Nf and initialize Nfpow */
        Nf = Math.pow (sm_a, 2.0) / (sm_b * Math.sqrt (1 + nuf2));
        Nfpow = Nf;
        	
        /* Precalculate tf */
        tf = Math.tan (phif);
        tf2 = tf * tf;
        tf4 = tf2 * tf2;
        
        /* Precalculate fractional coefficients for x**n in the equations
           below to simplify the expressions for latitude and longitude. */
        x1frac = 1.0 / (Nfpow * cf);
        
        Nfpow *= Nf;   /* now equals Nf**2) */
        x2frac = tf / (2.0 * Nfpow);
        
        Nfpow *= Nf;   /* now equals Nf**3) */
        x3frac = 1.0 / (6.0 * Nfpow * cf);
        
        Nfpow *= Nf;   /* now equals Nf**4) */
        x4frac = tf / (24.0 * Nfpow);
        
        Nfpow *= Nf;   /* now equals Nf**5) */
        x5frac = 1.0 / (120.0 * Nfpow * cf);
        
        Nfpow *= Nf;   /* now equals Nf**6) */
        x6frac = tf / (720.0 * Nfpow);
        
        Nfpow *= Nf;   /* now equals Nf**7) */
        x7frac = 1.0 / (5040.0 * Nfpow * cf);
        
        Nfpow *= Nf;   /* now equals Nf**8) */
        x8frac = tf / (40320.0 * Nfpow);
        
        /* Precalculate polynomial coefficients for x**n.
           -- x**1 does not have a polynomial coefficient. */
        x2poly = -1.0 - nuf2;
        
        x3poly = -1.0 - 2 * tf2 - nuf2;
        
        x4poly = 5.0 + 3.0 * tf2 + 6.0 * nuf2 - 6.0 * tf2 * nuf2
        	- 3.0 * (nuf2 *nuf2) - 9.0 * tf2 * (nuf2 * nuf2);
        
        x5poly = 5.0 + 28.0 * tf2 + 24.0 * tf4 + 6.0 * nuf2 + 8.0 * tf2 * nuf2;
        
        x6poly = -61.0 - 90.0 * tf2 - 45.0 * tf4 - 107.0 * nuf2
        	+ 162.0 * tf2 * nuf2;
        
        x7poly = -61.0 - 662.0 * tf2 - 1320.0 * tf4 - 720.0 * (tf4 * tf2);
        
        x8poly = 1385.0 + 3633.0 * tf2 + 4095.0 * tf4 + 1575 * (tf4 * tf2);
        	
        /* Calculate latitude */
        philambda[0] = phif + x2frac * x2poly * (x * x)
        	+ x4frac * x4poly * Math.pow (x, 4.0)
        	+ x6frac * x6poly * Math.pow (x, 6.0)
        	+ x8frac * x8poly * Math.pow (x, 8.0);
        	
        /* Calculate longitude */
        philambda[1] = lambda0 + x1frac * x
        	+ x3frac * x3poly * Math.pow (x, 3.0)
        	+ x5frac * x5poly * Math.pow (x, 5.0)
        	+ x7frac * x7poly * Math.pow (x, 7.0);
        	
        return;
    }




    /*
    * LatLonToUTMXY
    *
    * Converts a latitude/longitude pair to x and y coordinates in the
    * Universal Transverse Mercator projection.
    *
    * Inputs:
    *   lat - Latitude of the point, in radians.
    *   lon - Longitude of the point, in radians.
    *   zone - UTM zone to be used for calculating values for x and y.
    *          If zone is less than 1 or greater than 60, the routine
    *          will determine the appropriate zone from the value of lon.
    *
    * Outputs:
    *   xy - A 2-element array where the UTM x and y values will be stored.
    *
    * Returns:
    *   The UTM zone used for calculating the values of x and y.
    *
    */
    function LatLonToUTMXY (lat, lon, zone, xy)
    {
        MapLatLonToXY (lat, lon, UTMCentralMeridian (zone), xy);

        /* Adjust easting and northing for UTM system. */
        xy[0] = xy[0] * UTMScaleFactor + 500000.0;
        xy[1] = xy[1] * UTMScaleFactor;
        if (xy[1] < 0.0)
            xy[1] = xy[1] + 10000000.0;

        return zone;
    }
    
    
    
    /*
    * UTMXYToLatLon
    *
    * Converts x and y coordinates in the Universal Transverse Mercator
    * projection to a latitude/longitude pair.
    *
    * Inputs:
    *	x - The easting of the point, in meters.
    *	y - The northing of the point, in meters.
    *	zone - The UTM zone in which the point lies.
    *	southhemi - True if the point is in the southern hemisphere;
    *               false otherwise.
    *
    * Outputs:
    *	latlon - A 2-element array containing the latitude and
    *            longitude of the point, in radians.
    *
    * Returns:
    *	The function does not return a value.
    *
    */
    function UTMXYToLatLon (x, y, zone, southhemi, latlon)
    {
        var cmeridian;
        	
        x -= 500000.0;
        x /= UTMScaleFactor;
        	
        /* If in southern hemisphere, adjust y accordingly. */
        if (southhemi)
        y -= 10000000.0;
        		
        y /= UTMScaleFactor;
        
        cmeridian = UTMCentralMeridian (zone);
        MapXYToLatLon (x, y, cmeridian, latlon);
        	
        return;
    }
    


function lz(num, len)
{
	num = String(num);
	while (num.length < len)
	{
	   num = "0" + num;
	}

	return num;
}


function dec2utm(lat, lon)
{

	var xy = new Array(2);

        // Compute the UTM zone.
        zone = Math.floor ((lon + 180.0) / 6) + 1

        zone = LatLonToUTMXY (DegToRad (lat), DegToRad (lon), zone, xy);

	var hemi = 'N';

        if (lat < 0)
		hemi = 'S';

        /* Set the output controls.  */

	return Array(zone, hemi, xy[0], xy[1]);
}

function dec2dms(deg)
{
	var results = new Array();

	results['d'] = Math.floor(deg);
	var remainder = deg - (results['d'] * 1.0);
	var gpsmin = remainder * 60.0;
	results['m'] = Math.floor(gpsmin);
	var remainder2 = gpsmin - (parseInt(gpsmin)*1.0);
	results['s'] = Math.floor(remainder2*60.0);

	return results;
}

