"use strict"; function round2(x) { return Math.round(x * 100) / 100; } const DEG2RAD = Math.PI / 180; const RAD2DEG = 180 / Math.PI; function initialBearingDeg(lat1, lon1, lat2, lon2) { const la1 = lat1 * DEG2RAD; const la2 = lat2 * DEG2RAD; const dLon = (lon2 - lon1) * DEG2RAD; const y = Math.sin(dLon) * Math.cos(la2); const x = Math.cos(la1) * Math.sin(la2) - Math.sin(la1) * Math.cos(la2) * Math.cos(dLon); const theta = Math.atan2(y, x) * RAD2DEG; return (theta + 360) % 360; } module.exports = { slug: "initial-bearing-between-points", title: "Initial bearing between two points", deck_category: "astronomy", description: "Computes the initial compass bearing (forward azimuth) from one latitude/longitude point toward another along the great circle.", inputs: [ { name: "lat1", type: "number", unit: "degrees", required: true, description: "Latitude of the starting point.", min: -90, max: 90 }, { name: "lon1", type: "number", unit: "degrees", required: true, description: "Longitude of the starting point.", min: -180, max: 180 }, { name: "lat2", type: "number", unit: "degrees", required: true, description: "Latitude of the destination point.", min: -90, max: 90 }, { name: "lon2", type: "number", unit: "degrees", required: true, description: "Longitude of the destination point.", min: -180, max: 180 } ], outputs: [ { name: "bearing_degrees", unit: "degrees from true north, 0-360", description: "Initial great-circle bearing from point 1 to point 2." } ], steps: [ "Convert both points' latitudes and longitudes to radians.", "theta = atan2( sin(dLon)*cos(lat2), cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(dLon) ), where dLon = lon2 - lon1.", "Convert theta to degrees and normalize into the range 0 to 360 by adding 360 and taking the result modulo 360.", "Round to 2 decimals.", "This is the initial bearing only; the bearing changes continuously along a great-circle route except along the equator or a meridian." ], constants: [], sources: [ { kind: "url", value: "http://www.movable-type.co.uk/scripts/latlong.html", title: "Calculate distance, bearing and more between latitude/longitude points", publisher: "Movable Type Scripts (Chris Veness)", retrieved_utc: "2026-09-30T19:45:00Z", note: "Standard spherical-trigonometry formula for initial bearing, consistent with Ed Williams' Aviation Formulary." } ], disclaimer: null, questionText(inputs) { return `What is the initial compass bearing from (${inputs.lat1}, ${inputs.lon1}) toward (${inputs.lat2}, ${inputs.lon2})?`; }, run(inputs) { const { lat1, lon1, lat2, lon2 } = inputs || {}; for (const [name, v] of Object.entries({ lat1, lon1, lat2, lon2 })) { if (typeof v !== "number" || Number.isNaN(v)) throw new Error(`${name} must be a number`); } const b = round2(initialBearingDeg(lat1, lon1, lat2, lon2)); return { answer: `${b.toFixed(2)} degrees`, values: { bearing_degrees: b } }; }, examples: [ { name: "worked example", inputs: { lat1: 0, lon1: 0, lat2: 0, lon2: 90 }, expected: { answer: "90.00 degrees" }, check: "Both points are on the equator, so travel between them is due east along the equator; the formula must analytically reduce to exactly 90 degrees (sin(dLon)*1 over 1*0-0 = atan2(1,0) = 90 degrees), which it does." }, { name: "test vector: London to Paris", inputs: { lat1: 51.5074, lon1: -0.1278, lat2: 48.8566, lon2: 2.3522 }, expected: { answer: "148.12 degrees" }, check: "By hand: with dLon=2.48deg, y=sin(2.48deg)*cos(48.8566deg) and x=cos(51.5074deg)*sin(48.8566deg)-sin(51.5074deg)*cos(48.8566deg)*cos(2.48deg) are both small values with x negative and y positive, placing theta in the second quadrant (between 90 and 180 degrees); atan2(y,x) evaluates to about 148 degrees, consistent with Paris lying east-southeast of London." } ] };