- Add .wiki/ with Home, API-Reference, and Architecture pages - Add wiki-sync.yml workflow - Fix README: badge and install command now use @acamarata/qibla scope - Add Architecture and Documentation sections to README - Add README.md, CHANGELOG.md, LICENSE to files field - Update pack-check CI to verify README, CHANGELOG, LICENSE in tarball - Fix exports.import.types to ./dist/index.d.mts (matches tsup output) - Enable sourcemap: true in tsup config
3.6 KiB
Architecture
Overview
@acamarata/qibla is a pure math library. No external dependencies. No I/O. All functions are synchronous and stateless.
The source is a single TypeScript file (src/index.ts) compiled to dual CJS/ESM output via tsup.
Qibla Bearing: Forward Azimuth
qiblaAngle uses the forward azimuth formula from spherical trigonometry, also called the "initial bearing" on the great circle.
Given observer (φ₁, λ₁) and Ka'bah (φ₂, λ₂) in radians:
y = sin(λ₂ − λ₁) · cos(φ₂)
x = cos(φ₁) · sin(φ₂) − sin(φ₁) · cos(φ₂) · cos(λ₂ − λ₁)
θ = atan2(y, x)
atan2 produces a result in (−π, π]. Adding 360° and taking modulo 360 converts to the [0, 360) convention, where 0° = North, 90° = East.
This gives the bearing at the observer's location, not the arrival bearing at the Ka'bah. For a short trip within a city, the difference is negligible. For a trans-oceanic path, the bearing rotates continuously along the geodesic — qiblaGreatCircle shows this progression.
Great-Circle Path: Slerp
qiblaGreatCircle uses the Slerp (spherical linear interpolation) formula to generate uniformly spaced waypoints along the geodesic.
Step 1 — Convert to 3D unit vectors
Lat/lng are converted to 3D Cartesian unit vectors on the unit sphere:
x = cos(φ) · cos(λ)
y = cos(φ) · sin(λ)
z = sin(φ)
Step 2 — Compute the angular distance
The central angle d between the two points uses the formula:
d = 2 · asin( sqrt( sin²((φ₂−φ₁)/2) + cos(φ₁)·cos(φ₂)·sin²((λ₂−λ₁)/2) ) )
This is equivalent to the haversine formula. If d = 0, the observer is at the Ka'bah — return immediately.
Step 3 — Interpolate
For each interpolation parameter f ∈ [0, 1]:
A = sin((1−f)·d) / sin(d)
B = sin(f·d) / sin(d)
P = A·P₁ + B·P₂
where P₁ and P₂ are the 3D unit vectors. Convert the result back to lat/lng.
This is numerically stable for all separations except d = 0 (handled separately) and d = π (antipodal points, undefined great circle). For practical use — observer and Ka'bah are never antipodal — this is not a concern.
Haversine Distance
distanceKm implements the haversine formula:
a = sin²(Δφ/2) + cos(φ₁) · cos(φ₂) · sin²(Δλ/2)
c = 2 · atan2(√a, √(1−a))
d = R · c
where R = 6,371 km (WGS-84 volumetric mean radius).
The haversine formula is numerically stable for both small and large distances, unlike the simpler spherical law of cosines which loses precision for short arcs.
Compass Direction
compassDir and compassName divide the 360° circle into eight 45° sectors. The sector index is:
index = round(bearing / 45) mod 8
Rounding (not flooring) ensures each sector is centered on its cardinal/intercardinal direction: N covers 337.5–360° and 0–22.5°, NE covers 22.5–67.5°, and so on.
Ka'bah Coordinates
The Ka'bah center is fixed at 21.422511°N, 39.82615°E. These coordinates come from verified GPS data and match the values used by major Islamic authority applications. The value is a constant — no runtime fetching.
Build
src/index.ts → tsup → dist/index.cjs (CommonJS)
→ dist/index.mjs (ESM)
→ dist/index.d.ts (type definitions, CJS fallback)
→ dist/index.d.mts (type definitions, ESM)
tsup config uses platform: 'neutral' — the library has no Node.js-specific API calls and works identically in browsers, Deno, Bun, and all bundlers.