qibla/.wiki/Architecture.md
Aric Camarata 8eb0516119 feat: add wiki, fix README scoped package name, CI and package improvements
- 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
2026-03-08 16:37:26 -04:00

3.6 KiB
Raw Blame History

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((1f)·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, √(1a))
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.5360° and 022.5°, NE covers 22.567.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.


Home | API Reference