Home | History | Annotate | Download | only in navigation
      1 /*
      2  * To change this template, choose Tools | Templates
      3  * and open the template in the editor.
      4  */
      5 package jme3tools.navigation;
      6 
      7 
      8 
      9 /**
     10  * A utlity class for performing position calculations
     11  *
     12  * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
     13  *          Jakobus)
     14  * @version 1.0
     15  * @since 1.0
     16  */
     17 public class NavCalculator {
     18 
     19     private double distance;
     20     private double trueCourse;
     21 
     22     /* The earth's radius in meters */
     23     public static final int WGS84_EARTH_RADIUS = 6378137;
     24     private String strCourse;
     25 
     26     /* The sailing calculation type */
     27     public static final int MERCATOR = 0;
     28     public static final int GC = 1;
     29 
     30     /* The degree precision to use for courses */
     31     public static final int RL_CRS_PRECISION = 1;
     32 
     33     /* The distance precision to use for distances */
     34     public static final int RL_DIST_PRECISION = 1;
     35     public static final int METERS_PER_MINUTE = 1852;
     36 
     37     /**
     38      * Constructor
     39      * @param P1
     40      * @param P2
     41      * @param calcType
     42      * @since 1.0
     43      */
     44     public NavCalculator(Position P1, Position P2, int calcType) {
     45         switch (calcType) {
     46             case MERCATOR:
     47                 mercatorSailing(P1, P2);
     48                 break;
     49             case GC:
     50                 greatCircleSailing(P1, P2);
     51                 break;
     52         }
     53     }
     54 
     55     /**
     56      * Constructor
     57      * @since 1.0
     58      */
     59     public NavCalculator() {
     60     }
     61 
     62     /**
     63      * Determines a great circle track between two positions
     64      * @param p1 origin position
     65      * @param p2 destination position
     66      */
     67     public GCSailing greatCircleSailing(Position p1, Position p2) {
     68         return new GCSailing(new int[0], new float[0]);
     69     }
     70 
     71     /**
     72      * Determines a Rhumb Line course and distance between two points
     73      * @param p1 origin position
     74      * @param p2 destination position
     75      */
     76     public RLSailing rhumbLineSailing(Position p1, Position p2) {
     77         RLSailing rl = mercatorSailing(p1, p2);
     78         return rl;
     79     }
     80 
     81     /**
     82      * Determines the rhumb line course  and distance between two positions
     83      * @param p1 origin position
     84      * @param p2 destination position
     85      */
     86     public RLSailing mercatorSailing(Position p1, Position p2) {
     87 
     88         double dLat = computeDLat(p1.getLatitude(), p2.getLatitude());
     89         //plane sailing...
     90         if (dLat == 0) {
     91             RLSailing rl = planeSailing(p1, p2);
     92             return rl;
     93         }
     94 
     95         double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
     96         double dmp = (float) computeDMPClarkeSpheroid(p1.getLatitude(), p2.getLatitude());
     97 
     98         trueCourse = (float) Math.toDegrees(Math.atan(dLong / dmp));
     99         double degCrs = convertCourse((float) trueCourse, p1, p2);
    100         distance = (float) Math.abs(dLat / Math.cos(Math.toRadians(trueCourse)));
    101 
    102         RLSailing rl = new RLSailing(degCrs, (float) distance);
    103         trueCourse = rl.getCourse();
    104         strCourse = (dLat < 0 ? "S" : "N");
    105         strCourse += " " + trueCourse;
    106         strCourse += " " + (dLong < 0 ? "W" : "E");
    107         return rl;
    108 
    109     }
    110 
    111     /**
    112      * Calculate a plane sailing situation - i.e. where Lats are the same
    113      * @param p1
    114      * @param p2
    115      * @return
    116      * @since 1.0
    117      */
    118     public RLSailing planeSailing(Position p1, Position p2) {
    119         double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
    120 
    121         double sgnDLong = 0 - (dLong / Math.abs(dLong));
    122         if (Math.abs(dLong) > 180 * 60) {
    123             dLong = (360 * 60 - Math.abs(dLong)) * sgnDLong;
    124         }
    125 
    126         double redist = 0;
    127         double recourse = 0;
    128         if (p1.getLatitude() == 0) {
    129             redist = Math.abs(dLong);
    130         } else {
    131             redist = Math.abs(dLong * (float) Math.cos(p1.getLatitude() * 2 * Math.PI / 360));
    132         }
    133         recourse = (float) Math.asin(0 - sgnDLong);
    134         recourse = recourse * 360 / 2 / (float) Math.PI;
    135 
    136         if (recourse < 0) {
    137             recourse = recourse + 360;
    138         }
    139         return new RLSailing(recourse, redist);
    140     }
    141 
    142     /**
    143      * Converts a course from cardinal XddY to ddd notation
    144      * @param tc
    145      * @param p1 position one
    146      * @param p2 position two
    147      * @return
    148      * @since 1.0
    149      */
    150     public static double convertCourse(float tc, Position p1, Position p2) {
    151 
    152         double dLat = p1.getLatitude() - p2.getLatitude();
    153         double dLong = p1.getLongitude() - p2.getLongitude();
    154         //NE
    155         if (dLong >= 0 & dLat >= 0) {
    156             return Math.abs(tc);
    157         }
    158 
    159         //SE
    160         if (dLong >= 0 & dLat < 0) {
    161             return 180 - Math.abs(tc);
    162         }
    163 
    164         //SW
    165         if (dLong < 0 & dLat < 0) {
    166             return 180 + Math.abs(tc);
    167         }
    168 
    169         //NW
    170         if (dLong < 0 & dLat >= 0) {
    171             return 360 - Math.abs(tc);
    172         }
    173         return -1;
    174     }
    175 
    176     /**
    177      * Getter method for the distance between two points
    178      * @return distance
    179      * @since 1.0
    180      */
    181     public double getDistance() {
    182         return distance;
    183     }
    184 
    185     /**
    186      * Getter method for the true course
    187      * @return true course
    188      * @since 1.0
    189      */
    190     public double getTrueCourse() {
    191         return trueCourse;
    192     }
    193 
    194     /**
    195      * Getter method for the true course
    196      * @return true course
    197      * @since 1.0
    198      */
    199     public String getStrCourse() {
    200         return strCourse;
    201     }
    202 
    203     /**
    204      * Computes the difference in meridional parts for two latitudes in minutes
    205      * (based on Clark 1880 spheroid)
    206      * @param lat1
    207      * @param lat2
    208      * @return difference in minutes
    209      * @since 1.0
    210      */
    211     public static double computeDMPClarkeSpheroid(double lat1, double lat2) {
    212         double absLat1 = Math.abs(lat1);
    213         double absLat2 = Math.abs(lat2);
    214 
    215         double m1 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
    216                 + (absLat1 / 2)))) / Math.log(10))
    217                 - 23.268932 * Math.sin(Math.toRadians(absLat1))
    218                 - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat1)), 3)
    219                 - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat1)), 5));
    220 
    221         double m2 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
    222                 + (absLat2 / 2)))) / Math.log(10))
    223                 - 23.268932 * Math.sin(Math.toRadians(absLat2))
    224                 - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat2)), 3)
    225                 - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat2)), 5));
    226         if ((lat1 <= 0 && lat2 <= 0) || (lat1 > 0 && lat2 > 0)) {
    227             return Math.abs(m1 - m2);
    228         } else {
    229             return m1 + m2;
    230         }
    231     }
    232 
    233     /**
    234      * Computes the difference in meridional parts for a perfect sphere between
    235      * two degrees of latitude
    236      * @param lat1
    237      * @param lat2
    238      * @return difference in meridional parts between lat1 and lat2 in minutes
    239      * @since 1.0
    240      */
    241     public static float computeDMPWGS84Spheroid(float lat1, float lat2) {
    242         float absLat1 = Math.abs(lat1);
    243         float absLat2 = Math.abs(lat2);
    244 
    245         float m1 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat1 / 2))))
    246                 - 23.01358 * Math.sin(absLat1 - 0.05135) * Math.pow(Math.sin(absLat1), 3));
    247 
    248         float m2 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat2 / 2))))
    249                 - 23.01358 * Math.sin(absLat2 - 0.05135) * Math.pow(Math.sin(absLat2), 3));
    250 
    251         if (lat1 <= 0 & lat2 <= 0 || lat1 > 0 & lat2 > 0) {
    252             return Math.abs(m1 - m2);
    253         } else {
    254             return m1 + m2;
    255         }
    256     }
    257 
    258     /**
    259      * Predicts the position of a target for a given time in the future
    260      * @param time the number of seconds from now for which to predict the future
    261      *        position
    262      * @param speed the miles per minute that the target is traveling
    263      * @param currentLat the target's current latitude
    264      * @param currentLong the target's current longitude
    265      * @param course the target's current course in degrees
    266      * @return the predicted future position
    267      * @since 1.0
    268      */
    269     public static Position predictPosition(int time, double speed,
    270             double currentLat, double currentLong, double course) {
    271         Position futurePosition = null;
    272         course = Math.toRadians(course);
    273         double futureLong = currentLong + speed * time * Math.sin(course);
    274         double futureLat = currentLat + speed * time * Math.cos(course);
    275         try {
    276             futurePosition = new Position(futureLat, futureLong);
    277         } catch (InvalidPositionException ipe) {
    278             ipe.printStackTrace();
    279         }
    280         return futurePosition;
    281 
    282     }
    283 
    284     /**
    285      * Computes the coordinate of position B relative to an offset given
    286      * a distance and an angle.
    287      *
    288      * @param offset        The offset position.
    289      * @param bearing       The bearing between the offset and the coordinate
    290      *                      that you want to calculate.
    291      * @param distance      The distance, in meters, between the offset
    292      *                      and point B.
    293      * @return              The position of point B that is located from
    294      *                      given offset at given distance and angle.
    295      * @since 1.0
    296      */
    297     public static Position computePosition(Position initialPos, double heading,
    298             double distance) {
    299         if (initialPos == null) {
    300             return null;
    301         }
    302         double angle;
    303         if (heading < 90) {
    304             angle = heading;
    305         } else if (heading > 90 && heading < 180) {
    306             angle = 180 - heading;
    307         } else if (heading > 180 && heading < 270) {
    308             angle = heading - 180;
    309         } else {
    310             angle = 360 - heading;
    311         }
    312 
    313         Position newPosition = null;
    314 
    315         // Convert meters into nautical miles
    316         distance = distance * 0.000539956803;
    317         angle = Math.toRadians(angle);
    318         double initialLat = initialPos.getLatitude();
    319         double initialLong = initialPos.getLongitude();
    320         double dlat = distance * Math.cos(angle);
    321         dlat = dlat / 60;
    322         dlat = Math.abs(dlat);
    323         double newLat = 0;
    324         if ((heading > 270 && heading < 360) || (heading > 0 && heading < 90)) {
    325             newLat = initialLat + dlat;
    326         } else if (heading < 270 && heading > 90) {
    327             newLat = initialLat - dlat;
    328         }
    329         double meanLat = (Math.abs(dlat) / 2.0) + newLat;
    330         double dep = (Math.abs(dlat * 60)) * Math.tan(angle);
    331         double dlong = dep * (1.0 / Math.cos(Math.toRadians(meanLat)));
    332         dlong = dlong / 60;
    333         dlong = Math.abs(dlong);
    334         double newLong;
    335         if (heading > 180 && heading < 360) {
    336             newLong = initialLong - dlong;
    337         } else {
    338             newLong = initialLong + dlong;
    339         }
    340 
    341         if (newLong < -180) {
    342             double diff = Math.abs(newLong + 180);
    343             newLong = 180 - diff;
    344         }
    345 
    346         if (newLong > 180) {
    347             double diff = Math.abs(newLong + 180);
    348             newLong = (180 - diff) * -1;
    349         }
    350 
    351         if (heading == 0 || heading == 360 || heading == 180) {
    352             newLong = initialLong;
    353             newLat = initialLat + dlat;
    354         } else if (heading == 90 || heading == 270) {
    355             newLat = initialLat;
    356 //            newLong = initialLong + dlong; THIS WAS THE ORIGINAL (IT WORKED)
    357             newLong = initialLong - dlong;
    358         }
    359         try {
    360             newPosition = new Position(newLat,
    361                     newLong);
    362         } catch (InvalidPositionException ipe) {
    363             ipe.printStackTrace();
    364             System.out.println(newLat + "," + newLong);
    365         }
    366         return newPosition;
    367     }
    368 
    369     /**
    370      * Computes the difference in Longitude between two positions and assigns the
    371      * correct sign -westwards travel, + eastwards travel
    372      * @param lng1
    373      * @param lng2
    374      * @return difference in longitude
    375      * @since 1.0
    376      */
    377     public static double computeDLong(double lng1, double lng2) {
    378         if (lng1 - lng2 == 0) {
    379             return 0;
    380         }
    381 
    382         // both easterly
    383         if (lng1 >= 0 & lng2 >= 0) {
    384             return -(lng1 - lng2) * 60;
    385         }
    386         //both westerly
    387         if (lng1 < 0 & lng2 < 0) {
    388             return -(lng1 - lng2) * 60;
    389         }
    390 
    391         //opposite sides of Date line meridian
    392 
    393         //sum less than 180
    394         if (Math.abs(lng1) + Math.abs(lng2) < 180) {
    395             if (lng1 < 0 & lng2 > 0) {
    396                 return -(Math.abs(lng1) + Math.abs(lng2)) * 60;
    397             } else {
    398                 return Math.abs(lng1) + Math.abs(lng2) * 60;
    399             }
    400         } else {
    401             //sum greater than 180
    402             if (lng1 < 0 & lng2 > 0) {
    403                 return -(360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
    404             } else {
    405                 return (360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
    406             }
    407         }
    408     }
    409 
    410     /**
    411      * Computes the difference in Longitude between two positions and assigns the
    412      * correct sign -westwards travel, + eastwards travel
    413      * @param lng1
    414      * @param lng2
    415      * @return difference in longitude
    416      * @since 1.0
    417      */
    418     public static double computeLongDiff(double lng1, double lng2) {
    419         if (lng1 - lng2 == 0) {
    420             return 0;
    421         }
    422 
    423         // both easterly
    424         if (lng1 >= 0 & lng2 >= 0) {
    425             return Math.abs(-(lng1 - lng2) * 60);
    426         }
    427         //both westerly
    428         if (lng1 < 0 & lng2 < 0) {
    429             return Math.abs(-(lng1 - lng2) * 60);
    430         }
    431 
    432         if (lng1 == 0) {
    433             return Math.abs(lng2 * 60);
    434         }
    435 
    436         if (lng2 == 0) {
    437             return Math.abs(lng1 * 60);
    438         }
    439 
    440         return (Math.abs(lng1) + Math.abs(lng2)) * 60;
    441     }
    442 
    443     /**
    444      * Compute the difference in latitude between two positions
    445      * @param lat1
    446      * @param lat2
    447      * @return difference in latitude
    448      * @since 1.0
    449      */
    450     public static double computeDLat(double lat1, double lat2) {
    451         //same side of equator
    452 
    453         //plane sailing
    454         if (lat1 - lat2 == 0) {
    455             return 0;
    456         }
    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