28be776c84
- Prayer reminder: /prayer page with 5 daily times, countdown, alAdhan API - Dhikr counter: /dhikr page with 3 presets, custom counter, haptics, DB tracking - Qibla finder: /qibla page with compass SVG, DeviceOrientation, bearing to Kaaba - Halal Monitor: Leaflet + Google Places, 26 KL markers, geolocation, distance sort - BottomNav: added clock icon for prayer - Prisma: schema updates for DhikrSession, DhikrDay, DailyStreak, XpTransaction - Geo utility module - Weighted Traefik routing: Contabo 99% / Synology 1% cold standby
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 };
|