package graphtea.library.util;

/**
administration of geodetic ellipsoids of revolution and spheres.
author    hehl@tfh-berlin.de
version   17-May-2005
*/
public final class Ellipsoid {
    private String _name; // name of ellipsoid
    private double _a, _f; // semimajor axis and flattening

    /**
     * shortcuts for pre-defined ellipsoids of revolution.
     */
    public enum ELL {BESSEL, HAYFORD, WGS84}

    private static final String classname = "Ellipsoid";

    /**
     * parameters of Bessel ellipsoid
     */
    static final public Ellipsoid BESSEL = new Ellipsoid();

    /**
     * parameters of Hayford ellipsoid
     */
    static final public Ellipsoid HAYFORD = new Ellipsoid(ELL.HAYFORD);

    /**
     * parameters of WGS84 ellipsoid
     */
    static final public Ellipsoid WGS84 = new Ellipsoid(ELL.WGS84);

    /**
     *
     */
    private void init(String n, double a, double f) throws Msg {
        String r = classname + ".init";
        _name = n;
        _a = a;
        _f = f;
        if (null == n || n.isEmpty())
            throw new Msg(r, "name must not be empty");
        if (Math.abs(a - 6365.E3) > 5.E3)
            throw new Msg(r, String.format("invalid semimajor axis a= %.3f m", a));
        if (!isSphere() && (e2() < 0.006 || e2() > 0.008))
            throw new Msg(r, "invalid f; 1/f=" + 1. / f());
    }

    /**
     * default constructor - initializes the Bessel ellipsoid.
     */
    public Ellipsoid() {
        this(ELL.BESSEL);
    }

    private Ellipsoid(ELL id) {
        try {
            switch (id) {
                case HAYFORD:
                    init("Hayford", 6378388.0, 1. / 297.0);
                    break;
                case WGS84:
                    init("WGS84", 6378137.0, 1 / 298.257222);
                    break;
                default:
                    init("Bessel", 6377397.155, 1. / 299.15281285);
            }
        } catch (Exception ignored) {
        }
    }

    /**
     * generates a sphere of given radius.
     *
     * @param radius radius  of the sphere
     */
    public Ellipsoid(double radius) throws Msg {
        init("sphere", radius, 0.0);
    }

    /**
     * copy constructor.
     *
     * @param e a valid ellipsoid
     */
    public Ellipsoid(Ellipsoid e) {
        try {
            init(e.name(), e.a(), e.f());
        } catch (Exception ignored) {
        }
    }

    /**
     * creates a named sphere with given radius.
     *
     * @param n      name of the sphere
     * @param radius radius in meters
     */
    public Ellipsoid(String n, double radius) throws Msg {
        init(n, radius, 0.0);
    }

    /**
     * creates a named ellipsoid from semimajor axis and flattening.
     *
     * @param n name of ellipsoid
     * @param a semimajor axis in meters
     * @param f flattening
     */
    public Ellipsoid(String n, double a, double f) throws Msg {
        init(n, a, f);
    }

    /**
     * @return the name of the current ellipsoid
     */
    public String name() {
        return (_name);
    }

    /**
     * @return the full name of the current ellipsoid
     */
    public String fullname() {
        return (name() + (isSphere() ? " sphere" : " ellipsoid"));
    }

    /**
     * @return the semimajor axis (in meters) of the current ellipsoid
     */
    public double a() {
        return (_a);
    }

    /**
     * @return the semiminor axis (in meters) of the current ellipsoid
     */
    public double b() {
        return (a() * (1. - f()));
    }

    /**
     * @return the flattening of the current ellipsoid
     */
    public double f() {
        return (_f);
    }

    /**
     * @return the metric flattening of the current ellipsoid
     */
    public double a_minus_b() {
        return (a() * f());
    }

    /**
     * @return the square of the first numerical eccentricity
     */
    public double e2() {
        return (f() * (2. - f()));
    }

    /**
     * @return the first numerical eccentricity
     */
    public double e() {
        return (Math.sqrt(e2()));
    }

    /**
     * @return true if ellipsoid is a sphere
     */
    public boolean isSphere() {
        return (0.0 == f());
    }

    /**
     * @param phirad the ellipsoidal latitude in radians
     * @return the reduced latitude in radians
     */
    public double phi2beta(double phirad) {
        return (Math.atan(Math.tan(phirad) * (1. - f())));
    }

    /**
     * @param betarad the reduced latitude in radians
     * @return the ellipsoidal latitude in radians
     */
    public double beta2phi(double betarad) {
        return (Math.atan(Math.tan(betarad) / (1. - f())));
    }

    /**
     * @return a description of the current ellipsoid
     */
    public String toString() {
        String result = name() +
                (isSphere() ? " sphere\n R=" : " ellipsoid\n a=") +
                String.format("%.3f", a()) + " m";
        if (!isSphere()) result += "; 1/f= " +
                String.format("%.3f", 1. / f()) + "; e2=" +
                String.format("%.5f", e2());
        return result;
    }

    /**
     * test routine
     */
    public static void demo() {
        System.out.println("*********************************");
        System.out.println("*** Test of Class 'Ellipsoid' ***");
        System.out.println("*********************************");
        System.out.println();

        System.out.println(HAYFORD);
        try {
            System.out.println(new Ellipsoid("Test", 6.3E6, 1. / 300.));
            double phi = 50.0;
            final double RHODEG = 180. / Math.PI;
            Ellipsoid ell = BESSEL;
            System.out.println("on " + ell.name() + ":  phi=" + phi + " deg --> beta=" +
                    ell.phi2beta(phi / RHODEG) * RHODEG + " deg");
        } catch (Msg ignored) {
        }
    }

} // end class Ellipsoid
