/** * Calculate a position given an azimuth and distance from another point. * * <p> * * <p>Algorithm from National Geodetic Survey, FORTRAN program "forward," subroutine "DIRCT1," by * stephen j. frakes. http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html * * <p>Original documentation: * * <pre> * SOLUTION OF THE GEODETIC DIRECT PROBLEM AFTER T.VINCENTY * MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS * EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL * </pre> * * @param e Earth object (defines radius and flattening) * @param lat1 latitude of starting point * @param lon1 longitude of starting point * @param az forward azimuth (degrees) * @param dist distance from the point (km) * @param result Object to use if non-null * @return the position as a LatLonPointImpl */ public static LatLonPointImpl findPoint( Earth e, double lat1, double lon1, double az, double dist, LatLonPointImpl result) { if (result == null) { result = new LatLonPointImpl(); } if ((dist == 0)) { result.setLatitude(lat1); result.setLongitude(lon1); return result; } A = e.getMajor(); F = e.getFlattening(); R = 1.0 - F; // Algorithm from National Geodetic Survey, FORTRAN program "forward," // subroutine "DIRCT1," by stephen j. frakes. // http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html // Conversion to JAVA from FORTRAN was made with as few changes as // possible to avoid errors made while recasting form, and // to facilitate any future comparisons between the original // code and the altered version in Java. // Original documentation: // SUBROUTINE DIRCT1(GLAT1,GLON1,GLAT2,GLON2,FAZ,BAZ,S) // // SOLUTION OF THE GEODETIC DIRECT PROBLEM AFTER T.VINCENTY // MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS // EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL // // A IS THE SEMI-MAJOR AXIS OF THE REFERENCE ELLIPSOID // F IS THE FLATTENING OF THE REFERENCE ELLIPSOID // LATITUDES AND LONGITUDES IN RADIANS POSITIVE NORTH AND EAST // AZIMUTHS IN RADIANS CLOCKWISE FROM NORTH // GEODESIC DISTANCE S ASSUMED IN UNITS OF SEMI-MAJOR AXIS A // // PROGRAMMED FOR CDC-6600 BY LCDR L.PFEIFER NGS ROCKVILLE MD 20FEB75 // MODIFIED FOR SYSTEM 360 BY JOHN G GERGEN NGS ROCKVILLE MD 750608 // if (az < 0.0) { az += 360.0; // reset azs from -180 to 180 to 0 to 360 } double FAZ = az * rad; double GLAT1 = lat1 * rad; double GLON1 = lon1 * rad; double S = dist * 1000.; // convert to meters double TU = R * Math.sin(GLAT1) / Math.cos(GLAT1); double SF = Math.sin(FAZ); double CF = Math.cos(FAZ); double BAZ = 0.; if (CF != 0) { BAZ = Math.atan2(TU, CF) * 2; } double CU = 1. / Math.sqrt(TU * TU + 1.); double SU = TU * CU; double SA = CU * SF; double C2A = -SA * SA + 1.; double X = Math.sqrt((1. / R / R - 1.) * C2A + 1.) + 1.; X = (X - 2.) / X; double C = 1. - X; C = (X * X / 4. + 1) / C; double D = (0.375 * X * X - 1.) * X; TU = S / R / A / C; double Y = TU; double SY, CY, CZ, E, GLAT2, GLON2; do { SY = Math.sin(Y); CY = Math.cos(Y); CZ = Math.cos(BAZ + Y); E = CZ * CZ * 2. - 1.; C = Y; X = E * CY; Y = E + E - 1.; Y = (((SY * SY * 4. - 3.) * Y * CZ * D / 6. + X) * D / 4. - CZ) * SY * D + TU; } while (Math.abs(Y - C) > EPS); BAZ = CU * CY * CF - SU * SY; C = R * Math.sqrt(SA * SA + BAZ * BAZ); D = SU * CY + CU * SY * CF; GLAT2 = Math.atan2(D, C); C = CU * CY - SU * SY * CF; X = Math.atan2(SY * SF, C); C = ((-3. * C2A + 4.) * F + 4.) * C2A * F / 16.; D = ((E * CY * C + CZ) * SY * C + Y) * SA; GLON2 = GLON1 + X - (1. - C) * D * F; BAZ = (Math.atan2(SA, BAZ) + Math.PI) * deg; result.setLatitude(GLAT2 * deg); result.setLongitude(GLON2 * deg); return result; }
/** * Computes distance (in km), azimuth (degrees clockwise positive from North, 0 to 360), and back * azimuth (degrees clockwise positive from North, 0 to 360), from latitude-longituide point pt1 * to latitude-longituide pt2. * * <p>Algorithm from U.S. National Geodetic Survey, FORTRAN program "inverse," subroutine * "INVER1," by L. PFEIFER and JOHN G. GERGEN. See * http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html * * <p>Original documentation: <br> * SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY <br> * MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS <br> * EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL <br> * STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE Reference ellipsoid is the WGS-84 * ellipsoid. <br> * See http://www.colorado.edu/geography/gcraft/notes/datum/elist.html * * <p>Requires close to 1.4 E-5 seconds wall clock time per call on a 550 MHz Pentium with Linux * 7.2. * * @param e Earth object (defines radius and flattening) * @param lat1 Lat of point 1 * @param lon1 Lon of point 1 * @param lat2 Lat of point 2 * @param lon2 Lon of point 2 * @param result put result here, or null to allocate * @return a Bearing object with distance (in km), azimuth from pt1 to pt2 (degrees, 0 = north, * clockwise positive) */ public static Bearing calculateBearing( Earth e, double lat1, double lon1, double lat2, double lon2, Bearing result) { if (result == null) { result = new Bearing(); } if ((lat1 == lat2) && (lon1 == lon2)) { result.distance = 0; result.azimuth = 0; result.backazimuth = 0; return result; } A = e.getMajor(); F = e.getFlattening(); R = 1.0 - F; // Algorithm from National Geodetic Survey, FORTRAN program "inverse," // subroutine "INVER1," by L. PFEIFER and JOHN G. GERGEN. // http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html // Conversion to JAVA from FORTRAN was made with as few changes as possible // to avoid errors made while recasting form, and to facilitate any future // comparisons between the original code and the altered version in Java. // Original documentation: // SOLUTION OF THE GEODETIC INVERSE PROBLEM AFTER T.VINCENTY // MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS // EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL // STANDPOINT/FOREPOINT MUST NOT BE THE GEOGRAPHIC POLE // A IS THE SEMI-MAJOR AXIS OF THE REFERENCE ELLIPSOID // F IS THE FLATTENING (NOT RECIPROCAL) OF THE REFERNECE ELLIPSOID // LATITUDES GLAT1 AND GLAT2 // AND LONGITUDES GLON1 AND GLON2 ARE IN RADIANS POSITIVE NORTH AND EAST // FORWARD AZIMUTHS AT BOTH POINTS RETURNED IN RADIANS FROM NORTH // // Reference ellipsoid is the WGS-84 ellipsoid. // See http://www.colorado.edu/geography/gcraft/notes/datum/elist.html // FAZ is forward azimuth in radians from pt1 to pt2; // BAZ is backward azimuth from point 2 to 1; // S is distance in meters. // // Conversion to JAVA from FORTRAN was made with as few changes as possible // to avoid errors made while recasting form, and to facilitate any future // comparisons between the original code and the altered version in Java. // // IMPLICIT REAL*8 (A-H,O-Z) // COMMON/CONST/PI,RAD // COMMON/ELIPSOID/A,F double GLAT1 = rad * lat1; double GLAT2 = rad * lat2; double TU1 = R * Math.sin(GLAT1) / Math.cos(GLAT1); double TU2 = R * Math.sin(GLAT2) / Math.cos(GLAT2); double CU1 = 1. / Math.sqrt(TU1 * TU1 + 1.); double SU1 = CU1 * TU1; double CU2 = 1. / Math.sqrt(TU2 * TU2 + 1.); double S = CU1 * CU2; double BAZ = S * TU2; double FAZ = BAZ * TU1; double GLON1 = rad * lon1; double GLON2 = rad * lon2; double X = GLON2 - GLON1; double D, SX, CX, SY, CY, Y, SA, C2A, CZ, E, C; int loopCnt = 0; do { loopCnt++; // Check for an infinite loop if (loopCnt > 1000) { throw new IllegalArgumentException( "Too many iterations calculating bearing:" + lat1 + " " + lon1 + " " + lat2 + " " + lon2); } SX = Math.sin(X); CX = Math.cos(X); TU1 = CU2 * SX; TU2 = BAZ - SU1 * CU2 * CX; SY = Math.sqrt(TU1 * TU1 + TU2 * TU2); CY = S * CX + FAZ; Y = Math.atan2(SY, CY); SA = S * SX / SY; C2A = -SA * SA + 1.; CZ = FAZ + FAZ; if (C2A > 0.) { CZ = -CZ / C2A + CY; } E = CZ * CZ * 2. - 1.; C = ((-3. * C2A + 4.) * F + 4.) * C2A * F / 16.; D = X; X = ((E * CY * C + CZ) * SY * C + Y) * SA; X = (1. - C) * X * F + GLON2 - GLON1; // IF(DABS(D-X).GT.EPS) GO TO 100 } while (Math.abs(D - X) > EPS); if (loopCnt > maxLoopCnt) { maxLoopCnt = loopCnt; // System.err.println("loopCnt:" + loopCnt); } FAZ = Math.atan2(TU1, TU2); BAZ = Math.atan2(CU1 * SX, BAZ * CX - SU1 * CU2) + Math.PI; X = Math.sqrt((1. / R / R - 1.) * C2A + 1.) + 1.; X = (X - 2.) / X; C = 1. - X; C = (X * X / 4. + 1.) / C; D = (0.375 * X * X - 1.) * X; X = E * CY; S = 1. - E - E; S = ((((SY * SY * 4. - 3.) * S * CZ * D / 6. - X) * D / 4. + CZ) * SY * D + Y) * C * A * R; result.distance = S / 1000.0; // meters to km result.azimuth = FAZ * deg; // radians to degrees if (result.azimuth < 0.0) { result.azimuth += 360.0; // reset azs from -180 to 180 to 0 to 360 } result.backazimuth = BAZ * deg; // radians to degrees; already in 0 to 360 range return result; }