proj4
Version:
Proj4js is a JavaScript library to transform point coordinates from one coordinate system to another, including datum transformations.
124 lines (109 loc) • 5.43 kB
JavaScript
/**
* Calculates the inverse geodesic problem using Vincenty's formulae.
* Computes the forward azimuth and ellipsoidal distance between two points
* specified by latitude and longitude on the surface of an ellipsoid.
*
* @param {number} lat1 Latitude of the first point in radians.
* @param {number} lon1 Longitude of the first point in radians.
* @param {number} lat2 Latitude of the second point in radians.
* @param {number} lon2 Longitude of the second point in radians.
* @param {number} a Semi-major axis of the ellipsoid (meters).
* @param {number} f Flattening of the ellipsoid.
* @returns {{ azi1: number, s12: number }} An object containing:
* - azi1: Forward azimuth from the first point to the second point (radians).
* - s12: Ellipsoidal distance between the two points (meters).
*/
export function vincentyInverse(lat1, lon1, lat2, lon2, a, f) {
const L = lon2 - lon1;
const U1 = Math.atan((1 - f) * Math.tan(lat1));
const U2 = Math.atan((1 - f) * Math.tan(lat2));
const sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
const sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
let lambda = L, lambdaP, iterLimit = 100;
let sinLambda, cosLambda, sinSigma, cosSigma, sigma, sinAlpha, cos2Alpha, cos2SigmaM, C;
let uSq, A, B, deltaSigma, s;
do {
sinLambda = Math.sin(lambda);
cosLambda = Math.cos(lambda);
sinSigma = Math.sqrt(
(cosU2 * sinLambda) * (cosU2 * sinLambda)
+ (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)
* (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)
);
if (sinSigma === 0) {
return { azi1: 0, s12: 0 }; // coincident points
}
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
sigma = Math.atan2(sinSigma, cosSigma);
sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
cos2Alpha = 1 - sinAlpha * sinAlpha;
cos2SigmaM = (cos2Alpha !== 0) ? (cosSigma - 2 * sinU1 * sinU2 / cos2Alpha) : 0;
C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha));
lambdaP = lambda;
lambda = L + (1 - C) * f * sinAlpha
* (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
} while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);
if (iterLimit === 0) {
return { azi1: NaN, s12: NaN }; // formula failed to converge
}
uSq = cos2Alpha * (a * a - (a * (1 - f)) * (a * (1 - f))) / ((a * (1 - f)) * (a * (1 - f)));
A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)
- B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
s = (a * (1 - f)) * A * (sigma - deltaSigma);
// Forward azimuth
const azi1 = Math.atan2(cosU2 * sinLambda, cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
return { azi1, s12: s };
}
/**
* Solves the direct geodetic problem using Vincenty's formulae.
* Given a starting point, initial azimuth, and distance, computes the destination point on the ellipsoid.
*
* @param {number} lat1 Latitude of the starting point in radians.
* @param {number} lon1 Longitude of the starting point in radians.
* @param {number} azi1 Initial azimuth (forward azimuth) in radians.
* @param {number} s12 Distance to travel from the starting point in meters.
* @param {number} a Semi-major axis of the ellipsoid in meters.
* @param {number} f Flattening of the ellipsoid.
* @returns {{lat2: number, lon2: number}} The latitude and longitude (in radians) of the destination point.
*/
export function vincentyDirect(lat1, lon1, azi1, s12, a, f) {
const U1 = Math.atan((1 - f) * Math.tan(lat1));
const sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
const sinAlpha1 = Math.sin(azi1), cosAlpha1 = Math.cos(azi1);
const sigma1 = Math.atan2(sinU1, cosU1 * cosAlpha1);
const sinAlpha = cosU1 * sinAlpha1;
const cos2Alpha = 1 - sinAlpha * sinAlpha;
const uSq = cos2Alpha * (a * a - (a * (1 - f)) * (a * (1 - f))) / ((a * (1 - f)) * (a * (1 - f)));
const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
let sigma = s12 / ((a * (1 - f)) * A), sigmaP, iterLimit = 100;
let cos2SigmaM, sinSigma, cosSigma, deltaSigma;
do {
cos2SigmaM = Math.cos(2 * sigma1 + sigma);
sinSigma = Math.sin(sigma);
cosSigma = Math.cos(sigma);
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 = s12 / ((a * (1 - f)) * A) + deltaSigma;
} while (Math.abs(sigma - sigmaP) > 1e-12 && --iterLimit > 0);
if (iterLimit === 0) {
return { lat2: NaN, lon2: NaN };
}
const tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1;
const lat2 = Math.atan2(
sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1,
(1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)
);
const lambda = Math.atan2(
sinSigma * sinAlpha1,
cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1
);
const C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha));
const L = lambda - (1 - C) * f * sinAlpha
* (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
const lon2 = lon1 + L;
return { lat2, lon2 };
}