13ed852eea50f9d4cd633efb8c2b054b8e33c253cristy/*
23ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * To change this template, choose Tools | Templates
33ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * and open the template in the editor.
43ed852eea50f9d4cd633efb8c2b054b8e33c253cristy */
53ed852eea50f9d4cd633efb8c2b054b8e33c253cristypackage jme3tools.navigation;
63ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
73ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
83ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
93ed852eea50f9d4cd633efb8c2b054b8e33c253cristy/**
103ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * A utlity class for performing position calculations
113ed852eea50f9d4cd633efb8c2b054b8e33c253cristy *
123ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
133ed852eea50f9d4cd633efb8c2b054b8e33c253cristy *          Jakobus)
143ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * @version 1.0
153ed852eea50f9d4cd633efb8c2b054b8e33c253cristy * @since 1.0
16de984cdc3631106b1cbbb8d3972b76a0fc27e8e8cristy */
173ed852eea50f9d4cd633efb8c2b054b8e33c253cristypublic class NavCalculator {
183ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
193ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    private double distance;
207ce65e7125a4e1df1a274ce373c537a9df9c16cdCristy    private double trueCourse;
213ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
223ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /* The earth's radius in meters */
233ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int WGS84_EARTH_RADIUS = 6378137;
243ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    private String strCourse;
253ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
263ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /* The sailing calculation type */
273ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int MERCATOR = 0;
283ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int GC = 1;
293ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
303ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /* The degree precision to use for courses */
313ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int RL_CRS_PRECISION = 1;
323ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
333ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /* The distance precision to use for distances */
343ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int RL_DIST_PRECISION = 1;
353ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static final int METERS_PER_MINUTE = 1852;
363ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
373ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
383ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Constructor
393ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param P1
403ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param P2
413ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param calcType
424c08aed51c5899665ade97263692328eea4af106cristy     * @since 1.0
434c08aed51c5899665ade97263692328eea4af106cristy     */
444c08aed51c5899665ade97263692328eea4af106cristy    public NavCalculator(Position P1, Position P2, int calcType) {
454c08aed51c5899665ade97263692328eea4af106cristy        switch (calcType) {
464c08aed51c5899665ade97263692328eea4af106cristy            case MERCATOR:
474c08aed51c5899665ade97263692328eea4af106cristy                mercatorSailing(P1, P2);
484c08aed51c5899665ade97263692328eea4af106cristy                break;
494c08aed51c5899665ade97263692328eea4af106cristy            case GC:
504c08aed51c5899665ade97263692328eea4af106cristy                greatCircleSailing(P1, P2);
514c08aed51c5899665ade97263692328eea4af106cristy                break;
524c08aed51c5899665ade97263692328eea4af106cristy        }
534c08aed51c5899665ade97263692328eea4af106cristy    }
544c08aed51c5899665ade97263692328eea4af106cristy
554c08aed51c5899665ade97263692328eea4af106cristy    /**
564c08aed51c5899665ade97263692328eea4af106cristy     * Constructor
574c08aed51c5899665ade97263692328eea4af106cristy     * @since 1.0
584c08aed51c5899665ade97263692328eea4af106cristy     */
594c08aed51c5899665ade97263692328eea4af106cristy    public NavCalculator() {
604c08aed51c5899665ade97263692328eea4af106cristy    }
613ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
623ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
633ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Determines a great circle track between two positions
643ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p1 origin position
653ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p2 destination position
663ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
673ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public GCSailing greatCircleSailing(Position p1, Position p2) {
683ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return new GCSailing(new int[0], new float[0]);
693ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
703ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
713ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
723ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Determines a Rhumb Line course and distance between two points
733ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p1 origin position
743ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p2 destination position
753ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
763ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public RLSailing rhumbLineSailing(Position p1, Position p2) {
773ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        RLSailing rl = mercatorSailing(p1, p2);
783ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return rl;
793ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
803ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
813ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
823ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Determines the rhumb line course  and distance between two positions
833ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p1 origin position
843ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p2 destination position
853ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
863ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public RLSailing mercatorSailing(Position p1, Position p2) {
873ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
883ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dLat = computeDLat(p1.getLatitude(), p2.getLatitude());
893ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //plane sailing...
903ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (dLat == 0) {
913ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            RLSailing rl = planeSailing(p1, p2);
923ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return rl;
933ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
943ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
953ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
963ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dmp = (float) computeDMPClarkeSpheroid(p1.getLatitude(), p2.getLatitude());
973ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
983ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        trueCourse = (float) Math.toDegrees(Math.atan(dLong / dmp));
993ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double degCrs = convertCourse((float) trueCourse, p1, p2);
1003ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        distance = (float) Math.abs(dLat / Math.cos(Math.toRadians(trueCourse)));
1013ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1023ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        RLSailing rl = new RLSailing(degCrs, (float) distance);
1033ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        trueCourse = rl.getCourse();
1043ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        strCourse = (dLat < 0 ? "S" : "N");
1053ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        strCourse += " " + trueCourse;
1063ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        strCourse += " " + (dLong < 0 ? "W" : "E");
1073ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return rl;
1083ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1093ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
1103ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1113ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
1123ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Calculate a plane sailing situation - i.e. where Lats are the same
1133ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p1
1143ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p2
1153ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return
116952a6063b767d87c452b00435088197d38916dd8cristy     * @since 1.0
117952a6063b767d87c452b00435088197d38916dd8cristy     */
118952a6063b767d87c452b00435088197d38916dd8cristy    public RLSailing planeSailing(Position p1, Position p2) {
119952a6063b767d87c452b00435088197d38916dd8cristy        double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
120952a6063b767d87c452b00435088197d38916dd8cristy
1213ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double sgnDLong = 0 - (dLong / Math.abs(dLong));
122952a6063b767d87c452b00435088197d38916dd8cristy        if (Math.abs(dLong) > 180 * 60) {
1233ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            dLong = (360 * 60 - Math.abs(dLong)) * sgnDLong;
1243ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
1253ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
126952a6063b767d87c452b00435088197d38916dd8cristy        double redist = 0;
127952a6063b767d87c452b00435088197d38916dd8cristy        double recourse = 0;
128952a6063b767d87c452b00435088197d38916dd8cristy        if (p1.getLatitude() == 0) {
129952a6063b767d87c452b00435088197d38916dd8cristy            redist = Math.abs(dLong);
130952a6063b767d87c452b00435088197d38916dd8cristy        } else {
131952a6063b767d87c452b00435088197d38916dd8cristy            redist = Math.abs(dLong * (float) Math.cos(p1.getLatitude() * 2 * Math.PI / 360));
132952a6063b767d87c452b00435088197d38916dd8cristy        }
133952a6063b767d87c452b00435088197d38916dd8cristy        recourse = (float) Math.asin(0 - sgnDLong);
134952a6063b767d87c452b00435088197d38916dd8cristy        recourse = recourse * 360 / 2 / (float) Math.PI;
1353ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1363ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (recourse < 0) {
1373ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            recourse = recourse + 360;
1383ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
1393ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return new RLSailing(recourse, redist);
1403ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
1413ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1423ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
1433ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Converts a course from cardinal XddY to ddd notation
1443ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param tc
1453ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p1 position one
1463ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param p2 position two
1473ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return
1483ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
1493ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
1503ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static double convertCourse(float tc, Position p1, Position p2) {
151952a6063b767d87c452b00435088197d38916dd8cristy
152952a6063b767d87c452b00435088197d38916dd8cristy        double dLat = p1.getLatitude() - p2.getLatitude();
1533ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dLong = p1.getLongitude() - p2.getLongitude();
154952a6063b767d87c452b00435088197d38916dd8cristy        //NE
1553ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (dLong >= 0 & dLat >= 0) {
1563ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(tc);
1573ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
1583ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1593ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //SE
1603ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (dLong >= 0 & dLat < 0) {
1613ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return 180 - Math.abs(tc);
1623ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
1633ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1643ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //SW
1653ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (dLong < 0 & dLat < 0) {
1663ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return 180 + Math.abs(tc);
1673ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
1683ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1693ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //NW
170952a6063b767d87c452b00435088197d38916dd8cristy        if (dLong < 0 & dLat >= 0) {
171952a6063b767d87c452b00435088197d38916dd8cristy            return 360 - Math.abs(tc);
1723ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
173bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy        return -1;
1743ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
1753ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1763ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
1774c08aed51c5899665ade97263692328eea4af106cristy     * Getter method for the distance between two points
1783ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return distance
1793ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
1803ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
181c6da28e61bb609d2b2cfdcc7752106c973415edbcristy    public double getDistance() {
182c6da28e61bb609d2b2cfdcc7752106c973415edbcristy        return distance;
1833ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
1843ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
1853ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
1863ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Getter method for the true course
1873ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return true course
1883ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
1893ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
1903ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public double getTrueCourse() {
1913ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return trueCourse;
1923ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
1933ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
194e1c94d9d25db6b0dd7a5028ffee31d1057855d73cristy    /**
1953ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Getter method for the true course
1963ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return true course
1973ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
1983ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
199e1c94d9d25db6b0dd7a5028ffee31d1057855d73cristy    public String getStrCourse() {
2009950d57e1124b73f684fb5946e206994cefda628cristy        return strCourse;
2013ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
2023ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2033ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
2043ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Computes the difference in meridional parts for two latitudes in minutes
2053ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * (based on Clark 1880 spheroid)
2063ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lat1
207952a6063b767d87c452b00435088197d38916dd8cristy     * @param lat2
2083ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return difference in minutes
2093ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
2103ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
2113ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static double computeDMPClarkeSpheroid(double lat1, double lat2) {
2123ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double absLat1 = Math.abs(lat1);
2133ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double absLat2 = Math.abs(lat2);
2143ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2153ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double m1 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
2163ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                + (absLat1 / 2)))) / Math.log(10))
2173ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 23.268932 * Math.sin(Math.toRadians(absLat1))
2183ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat1)), 3)
2193ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat1)), 5));
2203ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2213ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double m2 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
2223ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                + (absLat2 / 2)))) / Math.log(10))
2233ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 23.268932 * Math.sin(Math.toRadians(absLat2))
2243ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat2)), 3)
2253ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat2)), 5));
2263ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if ((lat1 <= 0 && lat2 <= 0) || (lat1 > 0 && lat2 > 0)) {
2273ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(m1 - m2);
2283ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else {
2293ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return m1 + m2;
2301fe0b879964fa7797e3d68574d297922a47c4034Cristy        }
2313ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
2323ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
233f50886b34832135cf6e2e1458f2a324bb1704191cristy    /**
2343ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Computes the difference in meridional parts for a perfect sphere between
2353ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * two degrees of latitude
2363ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lat1
2373ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lat2
2383ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return difference in meridional parts between lat1 and lat2 in minutes
2393ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
2403ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
2413ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static float computeDMPWGS84Spheroid(float lat1, float lat2) {
2423ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        float absLat1 = Math.abs(lat1);
2433ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        float absLat2 = Math.abs(lat2);
2443ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2453ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        float m1 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat1 / 2))))
2463ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 23.01358 * Math.sin(absLat1 - 0.05135) * Math.pow(Math.sin(absLat1), 3));
2473ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2483ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        float m2 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat2 / 2))))
2493ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                - 23.01358 * Math.sin(absLat2 - 0.05135) * Math.pow(Math.sin(absLat2), 3));
2503ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2513ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lat1 <= 0 & lat2 <= 0 || lat1 > 0 & lat2 > 0) {
2523ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(m1 - m2);
2531fe0b879964fa7797e3d68574d297922a47c4034Cristy        } else {
2543ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return m1 + m2;
2553ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
2563ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
257b0a657e13c4aefba39c51292005427b47277869dcristy
258b0a657e13c4aefba39c51292005427b47277869dcristy    /**
259952a6063b767d87c452b00435088197d38916dd8cristy     * Predicts the position of a target for a given time in the future
260952a6063b767d87c452b00435088197d38916dd8cristy     * @param time the number of seconds from now for which to predict the future
261952a6063b767d87c452b00435088197d38916dd8cristy     *        position
262952a6063b767d87c452b00435088197d38916dd8cristy     * @param speed the miles per minute that the target is traveling
2633ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param currentLat the target's current latitude
2643ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param currentLong the target's current longitude
2653ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param course the target's current course in degrees
2663ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return the predicted future position
2673ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
268acabb847a592ca5e430c1c0949d03acfc0b78bb9cristy     */
269acabb847a592ca5e430c1c0949d03acfc0b78bb9cristy    public static Position predictPosition(int time, double speed,
270acabb847a592ca5e430c1c0949d03acfc0b78bb9cristy            double currentLat, double currentLong, double course) {
271952a6063b767d87c452b00435088197d38916dd8cristy        Position futurePosition = null;
272952a6063b767d87c452b00435088197d38916dd8cristy        course = Math.toRadians(course);
273952a6063b767d87c452b00435088197d38916dd8cristy        double futureLong = currentLong + speed * time * Math.sin(course);
2743ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double futureLat = currentLat + speed * time * Math.cos(course);
2753ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        try {
276d15e65928aec551b7388c2863de3e3e628e2e0ddcristy            futurePosition = new Position(futureLat, futureLong);
2773ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } catch (InvalidPositionException ipe) {
2783ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            ipe.printStackTrace();
2793ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
280bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy        return futurePosition;
2811fe0b879964fa7797e3d68574d297922a47c4034Cristy
2823ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
2833ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
2843ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
2853ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Computes the coordinate of position B relative to an offset given
286bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy     * a distance and an angle.
2873ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     *
2883ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param offset        The offset position.
2893ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param bearing       The bearing between the offset and the coordinate
2903ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     *                      that you want to calculate.
2913ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param distance      The distance, in meters, between the offset
2923ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     *                      and point B.
2931fe0b879964fa7797e3d68574d297922a47c4034Cristy     * @return              The position of point B that is located from
2943ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     *                      given offset at given distance and angle.
2953ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
2963ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
2973ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static Position computePosition(Position initialPos, double heading,
2983ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            double distance) {
2993ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (initialPos == null) {
3003ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return null;
3013ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3023ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double angle;
3033ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (heading < 90) {
3043ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            angle = heading;
3053ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else if (heading > 90 && heading < 180) {
3063ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            angle = 180 - heading;
307bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy        } else if (heading > 180 && heading < 270) {
308bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy            angle = heading - 180;
309acd2ed254c18c254a0ab5aafa06d1645e5d079d8cristy        } else {
3103ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            angle = 360 - heading;
3113ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3123ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3133ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        Position newPosition = null;
3143ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3153ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        // Convert meters into nautical miles
3163ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        distance = distance * 0.000539956803;
3174c08aed51c5899665ade97263692328eea4af106cristy        angle = Math.toRadians(angle);
3183ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double initialLat = initialPos.getLatitude();
3193ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double initialLong = initialPos.getLongitude();
3203ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dlat = distance * Math.cos(angle);
3213ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        dlat = dlat / 60;
3224c08aed51c5899665ade97263692328eea4af106cristy        dlat = Math.abs(dlat);
3233ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double newLat = 0;
3243ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if ((heading > 270 && heading < 360) || (heading > 0 && heading < 90)) {
3253ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLat = initialLat + dlat;
3263ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else if (heading < 270 && heading > 90) {
3274c08aed51c5899665ade97263692328eea4af106cristy            newLat = initialLat - dlat;
3283ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3293ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double meanLat = (Math.abs(dlat) / 2.0) + newLat;
3303ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dep = (Math.abs(dlat * 60)) * Math.tan(angle);
3313ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double dlong = dep * (1.0 / Math.cos(Math.toRadians(meanLat)));
3323ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        dlong = dlong / 60;
3334c08aed51c5899665ade97263692328eea4af106cristy        dlong = Math.abs(dlong);
3343ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        double newLong;
3353ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (heading > 180 && heading < 360) {
3363ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLong = initialLong - dlong;
3373ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else {
3383ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLong = initialLong + dlong;
3393ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3403ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3413ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (newLong < -180) {
3423ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            double diff = Math.abs(newLong + 180);
3433ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLong = 180 - diff;
3443ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3453ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3463ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (newLong > 180) {
3473ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            double diff = Math.abs(newLong + 180);
3483ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLong = (180 - diff) * -1;
349bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy        }
350bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy
351acd2ed254c18c254a0ab5aafa06d1645e5d079d8cristy        if (heading == 0 || heading == 360 || heading == 180) {
3523ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLong = initialLong;
3533ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLat = initialLat + dlat;
3543ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else if (heading == 90 || heading == 270) {
3553ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newLat = initialLat;
3563ed852eea50f9d4cd633efb8c2b054b8e33c253cristy//            newLong = initialLong + dlong; THIS WAS THE ORIGINAL (IT WORKED)
3574c08aed51c5899665ade97263692328eea4af106cristy            newLong = initialLong - dlong;
3583ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3593ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        try {
3603ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            newPosition = new Position(newLat,
3613ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                    newLong);
3624c08aed51c5899665ade97263692328eea4af106cristy        } catch (InvalidPositionException ipe) {
3633ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            ipe.printStackTrace();
3643ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            System.out.println(newLat + "," + newLong);
3653ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3663ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return newPosition;
3674c08aed51c5899665ade97263692328eea4af106cristy    }
3683ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3693ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
3703ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Computes the difference in Longitude between two positions and assigns the
3713ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * correct sign -westwards travel, + eastwards travel
3723ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lng1
3734c08aed51c5899665ade97263692328eea4af106cristy     * @param lng2
3743ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return difference in longitude
3753ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
3763ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
3773ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static double computeDLong(double lng1, double lng2) {
3783ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 - lng2 == 0) {
3793ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return 0;
3803ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3813ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3823ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        // both easterly
3833ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 >= 0 & lng2 >= 0) {
3843ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return -(lng1 - lng2) * 60;
385cee9711bbc334b5677d5ec4ea1cc70340d35ee35cristy        }
386c6da28e61bb609d2b2cfdcc7752106c973415edbcristy        //both westerly
3873ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 < 0 & lng2 < 0) {
3883ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return -(lng1 - lng2) * 60;
3893ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
3903ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
3913ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //opposite sides of Date line meridian
3923ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
393952a6063b767d87c452b00435088197d38916dd8cristy        //sum less than 180
3943ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (Math.abs(lng1) + Math.abs(lng2) < 180) {
3953ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            if (lng1 < 0 & lng2 > 0) {
3963ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                return -(Math.abs(lng1) + Math.abs(lng2)) * 60;
3973ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            } else {
3983ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                return Math.abs(lng1) + Math.abs(lng2) * 60;
3993ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            }
4003ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        } else {
4013ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            //sum greater than 180
4023ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            if (lng1 < 0 & lng2 > 0) {
4033ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                return -(360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
4043ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            } else {
4053ed852eea50f9d4cd633efb8c2b054b8e33c253cristy                return (360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
4063ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            }
4073ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
4083ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
4093ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4103ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
4113ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Computes the difference in Longitude between two positions and assigns the
4123ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * correct sign -westwards travel, + eastwards travel
4133ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lng1
4143ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lng2
4153ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return difference in longitude
4163ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
4173ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
418bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy    public static double computeLongDiff(double lng1, double lng2) {
4193ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 - lng2 == 0) {
4203ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return 0;
421bb50337b2a8a16ca7e903cc04ab195ff0fd47ae6cristy        }
4223ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4233ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        // both easterly
4243ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 >= 0 & lng2 >= 0) {
4253ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(-(lng1 - lng2) * 60);
42606b627a07ff44e1ff93ef1288c9f428066ded10ddirk        }
4273ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //both westerly
42808e9a113db499034abb5ad8d59b42f8eca3c641cdirk        if (lng1 < 0 & lng2 < 0) {
42908e9a113db499034abb5ad8d59b42f8eca3c641cdirk            return Math.abs(-(lng1 - lng2) * 60);
4303ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
4313ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4323ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng1 == 0) {
4333ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(lng2 * 60);
4343ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
4353ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4363ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lng2 == 0) {
4373ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return Math.abs(lng1 * 60);
4383ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
4393ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4403ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        return (Math.abs(lng1) + Math.abs(lng2)) * 60;
4413ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    }
4423ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4433ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    /**
4443ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * Compute the difference in latitude between two positions
4453ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lat1
4463ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @param lat2
4473ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @return difference in latitude
4483ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     * @since 1.0
4493ed852eea50f9d4cd633efb8c2b054b8e33c253cristy     */
4503ed852eea50f9d4cd633efb8c2b054b8e33c253cristy    public static double computeDLat(double lat1, double lat2) {
4513ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //same side of equator
4523ed852eea50f9d4cd633efb8c2b054b8e33c253cristy
4533ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        //plane sailing
4543ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        if (lat1 - lat2 == 0) {
4553ed852eea50f9d4cd633efb8c2b054b8e33c253cristy            return 0;
4563ed852eea50f9d4cd633efb8c2b054b8e33c253cristy        }
457
458        //both northerly
459        if (lat1 >= 0 & lat2 >= 0) {
460            return -(lat1 - lat2) * 60;
461        }
462        //both southerly
463        if (lat1 < 0 & lat2 < 0) {
464            return -(lat1 - lat2) * 60;
465        }
466
467        //opposite sides of equator
468        if (lat1 >= 0) {
469            //heading south
470            return -(Math.abs(lat1) + Math.abs(lat2));
471        } else {
472            //heading north
473            return (Math.abs(lat1) + Math.abs(lat2));
474        }
475    }
476
477    public static class Quadrant {
478
479        private static final Quadrant FIRST = new Quadrant(1, 1);
480        private static final Quadrant SECOND = new Quadrant(-1, 1);
481        private static final Quadrant THIRD = new Quadrant(-1, -1);
482        private static final Quadrant FOURTH = new Quadrant(1, -1);
483        private final int lonMultiplier;
484        private final int latMultiplier;
485
486        public Quadrant(final int xMultiplier, final int yMultiplier) {
487            this.lonMultiplier = xMultiplier;
488            this.latMultiplier = yMultiplier;
489        }
490
491        static Quadrant getQuadrant(double degrees, boolean invert) {
492            if (invert) {
493                if (degrees >= 0 && degrees <= 90) {
494                    return FOURTH;
495                } else if (degrees > 90 && degrees <= 180) {
496                    return THIRD;
497                } else if (degrees > 180 && degrees <= 270) {
498                    return SECOND;
499                }
500                return FIRST;
501            } else {
502                if (degrees >= 0 && degrees <= 90) {
503                    return FIRST;
504                } else if (degrees > 90 && degrees <= 180) {
505                    return SECOND;
506                } else if (degrees > 180 && degrees <= 270) {
507                    return THIRD;
508                }
509                return FOURTH;
510            }
511        }
512    }
513
514    /**
515     * Converts meters to degrees.
516     *
517     * @param meters            The meters that you want to convert into degrees.
518     * @return                  The degree equivalent of the given meters.
519     * @since 1.0
520     */
521    public static double toDegrees(double meters) {
522        return (meters / METERS_PER_MINUTE) / 60;
523    }
524
525    /**
526     * Computes the bearing between two points.
527     *
528     * @param p1
529     * @param p2
530     * @return
531     * @since 1.0
532     */
533    public static int computeBearing(Position p1, Position p2) {
534        int bearing;
535        double dLon = computeDLong(p1.getLongitude(), p2.getLongitude());
536        double y = Math.sin(dLon) * Math.cos(p2.getLatitude());
537        double x = Math.cos(p1.getLatitude()) * Math.sin(p2.getLatitude())
538                - Math.sin(p1.getLatitude()) * Math.cos(p2.getLatitude()) * Math.cos(dLon);
539        bearing = (int) Math.toDegrees(Math.atan2(y, x));
540        return bearing;
541    }
542
543    /**
544     * Computes the angle between two points.
545     *
546     * @param p1
547     * @param p2
548     * @return
549     */
550    public static int computeAngle(Position p1, Position p2) {
551        // cos (adj / hyp)
552        double adj = Math.abs(p1.getLongitude() - p2.getLongitude());
553        double opp = Math.abs(p1.getLatitude() - p2.getLatitude());
554        return (int) Math.toDegrees(Math.atan(opp / adj));
555
556//        int angle = (int)Math.atan2(p2.getLatitude() - p1.getLatitude(),
557//                p2.getLongitude() - p1.getLongitude());
558        //Actually it's ATan2(dy , dx) where dy = y2 - y1 and dx = x2 - x1, or ATan(dy / dx)
559    }
560
561    public static int computeHeading(Position p1, Position p2) {
562        int angle = computeAngle(p1, p2);
563        // NE
564        if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() >= p1.getLatitude()) {
565            return angle;
566        } else if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) {
567            // SE
568            return 90 + angle;
569        } else if (p2.getLongitude() <= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) {
570            // SW
571            return 270 - angle;
572        } else {
573            // NW
574            return 270 + angle;
575        }
576    }
577
578    public static void main(String[] args) {
579        try {
580            int pos = NavCalculator.computeHeading(new Position(0, 0), new Position(10, -10));
581//            System.out.println(pos.getLatitude() + "," + pos.getLongitude());
582            System.out.println(pos);
583        } catch (Exception e) {
584        }
585
586
587
588
589
590    }
591}
592