Vincenty’s formula as it is used in the script:
| a, b = major & minor semiaxes of the ellipsoid | |
| f = flattening (a−b)/a | |
| φ1, φ2 = geodetic latitude | |
| s = length of the geodesic | |
| α1, α2 = azimuths of the geodesic (initial/final bearing) | |
| tanU1 = (1−f).tanφ1 (U is ‘reduced latitude’) | |
| cosU1 = 1/√(1+tan²U1), sinU1 = tanU1.cosU1 (trig identities; §6) | |
| σ1 = atan2(tanU1, cosα1) | (1) |
| sinα = cosU1.sinα1 | (2) |
| cos²α = 1 − sin²α (trig identity; §6) | |
| u² = cos²α.(a²−b²)/b² | |
| A = 1+u²/16384.{4096+u².[−768+u².(320−175.u²)]} | (3) |
| B = u²/1024.{256+u².[−128+u².(74−47.u²)]} | (4) |
| σ = s / b.A (1st approximation), σ′ = 2π | |
| while abs(σ−σ′) > 10-12 { (i.e. 0.06mm) | |
| cos2σm = cos(2.σ1 + σ) | (5) |
| Δσ = B.sinσ.{cos2σm + B/4.[cosσ.(−1 + 2.cos²2σm) − B/6.cos2σm.(−3 + 4.sin²σ).(−3 + 4.cos²2σm)]} | (6) |
| σ′ = σ | |
| σ = s / b.A + Δσ | (7) |
| } | |
| φ2 = atan2(sinU1.cosσ + cosU1.sinσ.cosα1, (1−f).√[sin²α + (sinU1.sinσ − cosU1.cosσ.cosα1)²]) | (8) |
| λ = atan2(sinσ.sinα1, cosU1.cosσ − sinU1.sinσ.cosα1) | (9) |
| C = f/16.cos²α.[4+f.(4−3.cos²α)] | (10) |
| L = λ − (1−C).f.sinα.{σ+C.sinσ.[cos2σm + C.cosσ.(−1 + 2.cos²2σm)]} (difference in longitude) | (11) |
| α2 = atan2(sinα, −sinU1.sinσ + cosU1.cosσ.cosα1) (reverse azimuth) | (12) |
| p2 = (φ2, λ1+L) |
See below for the source code of the JavaScript implementation. The JavaScript should be quite straightforward to translate into other languages if required (perhaps with alternate approach to returning latitude/longitude object).
I offer these formulæ & scripts for free use and adaptation as my contribution to the open-source
info-sphere from which I have received so much. You are welcome to re-use these scripts [under
a simple attribution license, without
any warranty express or implied] provided solely that you retain my copyright notice and a
link to this page.
If you would like to show your appreciation and support continued development of these scripts,
I would most gratefully accept donations.
If you have any queries or find any problems, contact me at ku.oc.epyt-elbavom@oeg-stpircs.
© 2005-2012 Chris Veness
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vincenty Direct Solution of Geodesics on the Ellipsoid (c) Chris Veness 2005-2012 */
/* */
/* from: Vincenty direct formula - T Vincenty, "Direct and Inverse Solutions of Geodesics on the */
/* Ellipsoid with application of nested equations", Survey Review, vol XXII no 176, 1975 */
/* http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Calculates destination point given start point lat/long, bearing & distance,
* using Vincenty inverse formula for ellipsoids
*
* @param {Number} lat1, lon1: first point in decimal degrees
* @param {Number} brng: initial bearing in decimal degrees
* @param {Number} dist: distance along bearing in metres
* @returns (LatLon} destination point
*/
function destVincenty(lat1, lon1, brng, dist) {
var a = 6378137, b = 6356752.3142, f = 1/298.257223563; // WGS-84 ellipsiod
var s = dist;
var alpha1 = brng.toRad();
var sinAlpha1 = Math.sin(alpha1);
var cosAlpha1 = Math.cos(alpha1);
var tanU1 = (1-f) * Math.tan(lat1.toRad());
var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
var sigma1 = Math.atan2(tanU1, cosAlpha1);
var sinAlpha = cosU1 * sinAlpha1;
var cosSqAlpha = 1 - sinAlpha*sinAlpha;
var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
var sigma = s / (b*A), sigmaP = 2*Math.PI;
while (Math.abs(sigma-sigmaP) > 1e-12) {
var cos2SigmaM = Math.cos(2*sigma1 + sigma);
var sinSigma = Math.sin(sigma);
var cosSigma = Math.cos(sigma);
var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
sigmaP = sigma;
sigma = s / (b*A) + deltaSigma;
}
var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
(1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
var L = lambda - (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
var lon2 = (lon1.toRad()+L+3*Math.PI)%(2*Math.PI) - Math.PI; // normalise to -180...+180
var revAz = Math.atan2(sinAlpha, -tmp); // final bearing, if required
return { lat: lat2.toDeg(), lon: lon2.toDeg(), finalBearing: revAz.toDeg() };
}
// ---- extend Number object with methods for converting degrees/radians
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */