All files / core/geo geodesic.ts

100% Statements 38/38
100% Branches 2/2
100% Functions 1/1
100% Lines 36/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80            5x   5x               8741x 8741x 8741x   8741x 8741x 8741x 8741x 8741x 8741x 8741x 8741x 8741x   8741x 8741x 8741x 8741x 8741x 30156x 30156x 30156x   30156x                 30156x 30156x 30156x 30156x   8741x 8741x 8741x   8741x 8741x       8741x       8741x   8741x                 8741x    
/**
 * Geodesic direct problem on WGS84 (Vincenty, 1975): the point at a distance and
 * initial bearing from a start point. Accurate to well under a millimetre.
 */
import { DEG, type LatLon, RAD, WGS84, b, wrapLon } from './ellipsoid'
 
export const METRES_PER_NAUTICAL_MILE = 1852
 
const { a, f } = WGS84
 
/** Destination from `start` after `distance` metres on initial bearing `bearing` degrees from true north. */
export function destination(
  start: LatLon,
  bearing: number,
  distance: number,
): LatLon {
  const alpha1 = bearing * DEG
  const sinAlpha1 = Math.sin(alpha1)
  const cosAlpha1 = Math.cos(alpha1)
 
  const tanU1 = (1 - f) * Math.tan(start.lat * DEG)
  const cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1)
  const sinU1 = tanU1 * cosU1
  const sigma1 = Math.atan2(tanU1, cosAlpha1)
  const sinAlpha = cosU1 * sinAlpha1
  const cosSqAlpha = 1 - sinAlpha * sinAlpha
  const uSq = (cosSqAlpha * (a * a - b * b)) / (b * b)
  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 = distance / (b * A)
  let cos2SigmaM = 0
  let sinSigma = 0
  let cosSigma = 0
  for (let i = 0; i < 100; i++) {
    cos2SigmaM = Math.cos(2 * sigma1 + sigma)
    sinSigma = Math.sin(sigma)
    cosSigma = Math.cos(sigma)
    const deltaSigma =
      B *
      sinSigma *
      (cos2SigmaM +
        (B / 4) *
          (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
            (B / 6) *
              cos2SigmaM *
              (-3 + 4 * sinSigma * sinSigma) *
              (-3 + 4 * cos2SigmaM * cos2SigmaM)))
    const next = distance / (b * A) + deltaSigma
    const converged = Math.abs(next - sigma) < 1e-12
    sigma = next
    if (converged) break
  }
  sinSigma = Math.sin(sigma)
  cosSigma = Math.cos(sigma)
  cos2SigmaM = Math.cos(2 * sigma1 + sigma)
 
  const x = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1
  const lat2 = Math.atan2(
    sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1,
    (1 - f) * Math.sqrt(sinAlpha * sinAlpha + x * x),
  )
  const lambda = Math.atan2(
    sinSigma * sinAlpha1,
    cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1,
  )
  const C = (f / 16) * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha))
  const L =
    lambda -
    (1 - C) *
      f *
      sinAlpha *
      (sigma +
        C *
          sinSigma *
          (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)))
 
  return { lat: lat2 * RAD, lon: wrapLon(start.lon + L * RAD) }
}