/** * 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 }; }