97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
|
|
/**
|
|||
|
|
* Qibla direction calculation utilities.
|
|||
|
|
*
|
|||
|
|
* Computes the bearing (direction) from a user's location to the Kaaba in Mecca.
|
|||
|
|
* Also provides distance to Mecca using the haversine formula.
|
|||
|
|
*
|
|||
|
|
* Kaaba coordinates: 21.4225° N, 39.8262° E
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const KAABA_LAT = 21.4225;
|
|||
|
|
const KAABA_LNG = 39.8262;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Convert degrees to radians.
|
|||
|
|
*/
|
|||
|
|
function toRadians(deg: number): number {
|
|||
|
|
return (deg * Math.PI) / 180;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Convert radians to degrees.
|
|||
|
|
*/
|
|||
|
|
function toDegrees(rad: number): number {
|
|||
|
|
return (rad * 180) / Math.PI;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Calculate the bearing (direction) from a location to the Kaaba in Mecca.
|
|||
|
|
*
|
|||
|
|
* Uses the standard great-circle bearing formula:
|
|||
|
|
* θ = atan2(sin(Δλ)·cos(φ₂), cos(φ₁)·sin(φ₂) − sin(φ₁)·cos(φ₂)·cos(Δλ))
|
|||
|
|
*
|
|||
|
|
* @param lat User's latitude (degrees)
|
|||
|
|
* @param lng User's longitude (degrees)
|
|||
|
|
* @returns Bearing in degrees clockwise from North (0–360)
|
|||
|
|
*/
|
|||
|
|
export function qiblaBearing(lat: number, lng: number): number {
|
|||
|
|
const φ1 = toRadians(lat);
|
|||
|
|
const φ2 = toRadians(KAABA_LAT);
|
|||
|
|
const Δλ = toRadians(KAABA_LNG - lng);
|
|||
|
|
|
|||
|
|
const y = Math.sin(Δλ) * Math.cos(φ2);
|
|||
|
|
const x =
|
|||
|
|
Math.cos(φ1) * Math.sin(φ2) -
|
|||
|
|
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
|
|||
|
|
|
|||
|
|
let bearing = toDegrees(Math.atan2(y, x));
|
|||
|
|
// Normalize to 0–360
|
|||
|
|
return (bearing + 360) % 360;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Calculate the distance from a location to the Kaaba in Mecca.
|
|||
|
|
*
|
|||
|
|
* @param lat User's latitude (degrees)
|
|||
|
|
* @param lng User's longitude (degrees)
|
|||
|
|
* @returns Distance in kilometres, rounded to 1 decimal place
|
|||
|
|
*/
|
|||
|
|
export function distanceToMecca(lat: number, lng: number): number {
|
|||
|
|
const EARTH_RADIUS_KM = 6371;
|
|||
|
|
const dLat = toRadians(KAABA_LAT - lat);
|
|||
|
|
const dLng = toRadians(KAABA_LNG - lng);
|
|||
|
|
|
|||
|
|
const a =
|
|||
|
|
Math.sin(dLat / 2) ** 2 +
|
|||
|
|
Math.cos(toRadians(lat)) *
|
|||
|
|
Math.cos(toRadians(KAABA_LAT)) *
|
|||
|
|
Math.sin(dLng / 2) ** 2;
|
|||
|
|
|
|||
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|||
|
|
|
|||
|
|
return Math.round(EARTH_RADIUS_KM * c * 10) / 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get a human-readable cardinal direction from a bearing.
|
|||
|
|
*
|
|||
|
|
* @param bearing Bearing in degrees (0–360)
|
|||
|
|
* @returns Cardinal direction string (e.g., "NE")
|
|||
|
|
*/
|
|||
|
|
export function bearingToDirection(bearing: number): string {
|
|||
|
|
const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
|
|||
|
|
const index = Math.round(bearing / 45) % 8;
|
|||
|
|
return dirs[index];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Format distance in a human-readable way.
|
|||
|
|
*/
|
|||
|
|
export function formatDistance(km: number): string {
|
|||
|
|
if (km < 1) return `${Math.round(km * 1000)} m`;
|
|||
|
|
if (km < 100) return `${km.toFixed(1)} km`;
|
|||
|
|
return `${Math.round(km).toLocaleString()} km`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export { KAABA_LAT, KAABA_LNG };
|