// Constants for WGS84
WGS84_a = 6378137.0;
WGS84_f = 1.0 / 298.257223563;
WGS84_one_f = 1.0 - WGS84_f;
WGS84_b = WGS84_a * WGS84_one_f; // semi-minor axis
WGS84_e2 = WGS84_f * (2.0 - WGS84_f); // first eccentricity squared
WGS84_epsilon = WGS84_e2 / (1.0 - WGS84_e2); // second eccentricity squared

function GeoPoint(lat, lng, alt)
{
    this.latitude = lat;
    this.longitude = lng;
    this.altitude = alt;
    return this;
}

GeoPoint.prototype = {
    load: function(txt)
    {
        var xy = txt.split(' ');
        this.latitude = parseFloat(xy[0]);
        this.longitude = parseFloat(xy[1]);
        if (xy.length > 2)
        {
            this.altitude = parseFloat(xy[2]);
        }
    },

    distanceTo: function(other)
    {
        return this.toEcefPoint().distanceTo(other.toEcefPoint());
    },

    toEcefPoint: function()
    {
        var lat = this.latitude * Math.PI / 180;
        var sinlat = Math.sin(lat);
        var coslat = Math.cos(lat);
        var lng = this.longitude * Math.PI / 180;
        var sinlng = Math.sin(lng);
        var coslng = Math.cos(lng);

        var N = WGS84_a / Math.sqrt(1 - (WGS84_e2 * sinlat * sinlat));
        var alt = this.altitude ? this.altitude : 0;
        var x = (N + alt) * coslat * coslng;
        var y = (N + alt) * coslat * sinlng;
        var z = ((1 - WGS84_e2) * N + alt) * sinlat;
        return new EcefPoint(x, y, z);
    },

    toString: function()
    {
        var description = Math.abs(this.latitude);
        if (this.latitude < 0)
        {
            description += " South, ";
        }
        else
        {
            description += " North, ";
        }

        description += Math.abs(this.longitude);
        if (this.longitude < 0)
        {
            description += " West";
        }
        else
        {
            description += " East";
        }

        if (this.altitude)
        {
            description += ', ' + this.altitude + ' metres altitude';
        }
        return description;
    }
}

if (google.maps.LatLng)
{
    GeoPoint.prototype.toGLatLng = function()
    {
	return new google.maps.LatLng(this.latitude, this.longitude);
    }
}

function EcefPoint(_x, _y, _z)
{
    if (_x instanceof Array)
    {
        this.x = _x[0];
        this.y = _x[1];
        this.z = _x[2];
    }
    else
    {
        this.x = _x;
        this.y = _y;
        this.z = _z;
    }
}

EcefPoint.prototype = {
    distanceTo: function(other)
    {
        return Math.sqrt(Math.pow(this.x - other.x, 2)
                         + Math.pow(this.y - other.y, 2)
                         + Math.pow(this.z - other.z, 2));
    },

    toGeoPoint: function()
    {
        if (this.x == 0 && this.y == 0 && this.z == 0)
        {
            return new GeoPoint(0, 0, 0);
        }
        if (this.x == 0 & this.y == 0 && this.z != 0)
        {
            return new GeoPoint((this.z > 0 ? 1 : -1) * Math.PI / 2,
                                0, Math.abs(this.z) - WGS84_b);
        }
        var p2 = this.x * this.x + this.y * this.y;
        var p = Math.sqrt(p2);
        if (this.z == 0)
        {
            return new GeoPoint(0, Math.atan2(y, x), p - WGS84_a);
        }
        var r = Math.sqrt(p2 + this.z * this.z);

        var tanu = WGS84_one_f * (this.z / p)
                   * (1 + (WGS84_epsilon * WGS84_b / r));
        // some fancy trig to work out cos3u and sin3u
        var cos2u = 1 / (1 + tanu * tanu);
        var cosu = Math.sqrt(cos2u);
        var cos3u = cosu * cos2u;
        var sin3u = (1 - cos2u) * tanu * cosu;

        var tanlat = (this.z + WGS84_epsilon * WGS84_b * sin3u)
                     / (p - (WGS84_e2 * WGS84_a * cos3u));

        var lat = Math.atan(tanlat);
        var lng = Math.atan2(this.y, this.x);

        // some more fancy trig to get sinlat and coslat
        var cos2lat = 1 / (1 + tanlat * tanlat);
        var sin2lat = 1 - cos2lat;
        var coslat = Math.sqrt(cos2lat);
        var sinlat = tanlat * coslat;

        var alt = p * coslat + this.z * sinlat
                  - WGS84_a * Math.sqrt(1 - (WGS84_e2 * sin2lat));

        return new GeoPoint(lat * 180 / Math.PI, lng * 180 / Math.PI, alt);
    },

    toArray: function()
    {
        return [this.x, this.y, this.z];
    },

    toString: function()
    {
        return '[' + this.x + ', ' + this.y + ', ' + this.z + ']';
    }
}
