mirror of
https://github.com/acamarata/qibla.git
synced 2026-06-30 19:04:28 +00:00
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
This commit is contained in:
parent
6179da5a45
commit
8eb0516119
8 changed files with 409 additions and 9 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -79,4 +79,7 @@ jobs:
|
|||
grep "dist/index.mjs" pack-output.txt
|
||||
grep "dist/index.d.ts" pack-output.txt
|
||||
grep "dist/index.d.mts" pack-output.txt
|
||||
grep "README.md" pack-output.txt
|
||||
grep "CHANGELOG.md" pack-output.txt
|
||||
grep "LICENSE" pack-output.txt
|
||||
echo "Pack check passed"
|
||||
|
|
|
|||
38
.github/workflows/wiki-sync.yml
vendored
Normal file
38
.github/workflows/wiki-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
name: Sync Wiki
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['.wiki/**']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout wiki
|
||||
run: |
|
||||
git clone "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git" .wiki-remote \
|
||||
|| (mkdir -p .wiki-remote \
|
||||
&& cd .wiki-remote \
|
||||
&& git init \
|
||||
&& git remote add origin "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git")
|
||||
|
||||
- name: Sync wiki pages
|
||||
run: |
|
||||
cp .wiki/*.md .wiki-remote/
|
||||
cd .wiki-remote
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "No wiki changes to commit"
|
||||
else
|
||||
git commit -m "Sync wiki from repo"
|
||||
git push
|
||||
fi
|
||||
170
.wiki/API-Reference.md
Normal file
170
.wiki/API-Reference.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# API Reference
|
||||
|
||||
## Functions
|
||||
|
||||
### `qiblaAngle(lat, lng)`
|
||||
|
||||
Computes the initial bearing from the given coordinates to the Ka'bah.
|
||||
|
||||
Uses the forward azimuth formula from spherical trigonometry:
|
||||
|
||||
```
|
||||
θ = atan2(sin(Δλ)·cos(φ₂), cos(φ₁)·sin(φ₂) − sin(φ₁)·cos(φ₂)·cos(Δλ))
|
||||
```
|
||||
|
||||
where φ₁, λ₁ is the observer and φ₂, λ₂ is the Ka'bah.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----- | -------- | ------------------------------------------ |
|
||||
| `lat` | `number` | Observer latitude in decimal degrees. Valid range: −90 to 90. |
|
||||
| `lng` | `number` | Observer longitude in decimal degrees. Valid range: −180 to 180. |
|
||||
|
||||
**Returns:** `number` — Bearing in degrees clockwise from true north. Range: [0, 360).
|
||||
|
||||
**Throws:** `RangeError` if either coordinate is out of bounds.
|
||||
|
||||
```typescript
|
||||
import { qiblaAngle } from '@acamarata/qibla';
|
||||
|
||||
qiblaAngle(40.7128, -74.006); // ~58.48 (New York → Mecca)
|
||||
qiblaAngle(51.5074, -0.1278); // ~119.0 (London → Mecca)
|
||||
qiblaAngle(35.6762, 139.6503); // ~293.3 (Tokyo → Mecca)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `compassDir(bearing)`
|
||||
|
||||
Returns the eight-point compass abbreviation for a bearing.
|
||||
|
||||
Maps the bearing to one of eight 45° sectors, selecting the nearest cardinal or intercardinal direction.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | -------- | ---------------------------- |
|
||||
| `bearing` | `number` | Bearing in degrees (0–360). |
|
||||
|
||||
**Returns:** `CompassAbbr` — One of: `N`, `NE`, `E`, `SE`, `S`, `SW`, `W`, `NW`.
|
||||
|
||||
```typescript
|
||||
import { compassDir } from '@acamarata/qibla';
|
||||
|
||||
compassDir(0); // "N"
|
||||
compassDir(45); // "NE"
|
||||
compassDir(58.5); // "NE"
|
||||
compassDir(270); // "W"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `compassName(bearing)`
|
||||
|
||||
Returns the full compass direction name for a bearing.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | -------- | ---------------------------- |
|
||||
| `bearing` | `number` | Bearing in degrees (0–360). |
|
||||
|
||||
**Returns:** `CompassName` — One of: `North`, `Northeast`, `East`, `Southeast`, `South`, `Southwest`, `West`, `Northwest`.
|
||||
|
||||
```typescript
|
||||
import { compassName } from '@acamarata/qibla';
|
||||
|
||||
compassName(58.5); // "Northeast"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `qiblaGreatCircle(lat, lng, steps?)`
|
||||
|
||||
Generates waypoints along the great-circle geodesic from the observer to the Ka'bah, using the Slerp (spherical linear interpolation) formula.
|
||||
|
||||
Useful for drawing the Qibla direction line on a map. Returns `steps + 1` points uniformly spaced along the geodesic.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------- | -------- | ------- | ------------------------------------------------ |
|
||||
| `lat` | `number` | — | Observer latitude in decimal degrees (−90 to 90). |
|
||||
| `lng` | `number` | — | Observer longitude in decimal degrees (−180 to 180). |
|
||||
| `steps` | `number` | `120` | Number of segments. Result has `steps + 1` points. |
|
||||
|
||||
**Returns:** `[number, number][]` — Array of `[latitude, longitude]` pairs in degrees.
|
||||
|
||||
**Throws:** `RangeError` if coordinates are out of bounds.
|
||||
|
||||
Special case: if the observer is at the Ka'bah (distance = 0), returns `[[lat, lng]]`.
|
||||
|
||||
```typescript
|
||||
import { qiblaGreatCircle } from '@acamarata/qibla';
|
||||
|
||||
const path = qiblaGreatCircle(40.7128, -74.006); // 121 points
|
||||
// path[0] ≈ [40.7128, -74.006] (New York)
|
||||
// path[120] ≈ [21.42, 39.83] (Ka'bah)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `distanceKm(lat1, lng1, lat2, lng2)`
|
||||
|
||||
Haversine distance between two points in kilometers.
|
||||
|
||||
Uses the haversine formula with a spherical Earth (R = 6,371 km, WGS-84 volumetric mean). Accurate to within 0.5% globally.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | -------- | ------------------------------------- |
|
||||
| `lat1` | `number` | First point latitude in decimal degrees. |
|
||||
| `lng1` | `number` | First point longitude in decimal degrees. |
|
||||
| `lat2` | `number` | Second point latitude in decimal degrees. |
|
||||
| `lng2` | `number` | Second point longitude in decimal degrees. |
|
||||
|
||||
**Returns:** `number` — Distance in kilometers.
|
||||
|
||||
```typescript
|
||||
import { distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||
|
||||
distanceKm(40.7128, -74.006, KAABA_LAT, KAABA_LNG); // ~9,634 km
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
| Constant | Value | Description |
|
||||
| ----------------- | ----------- | --------------------------------------------------- |
|
||||
| `KAABA_LAT` | `21.422511` | Ka'bah center latitude in decimal degrees north. |
|
||||
| `KAABA_LNG` | `39.82615` | Ka'bah center longitude in decimal degrees east. |
|
||||
| `EARTH_RADIUS_KM` | `6371` | WGS-84 volumetric mean radius in kilometers. |
|
||||
|
||||
```typescript
|
||||
import { KAABA_LAT, KAABA_LNG, EARTH_RADIUS_KM } from '@acamarata/qibla';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### `CompassAbbr`
|
||||
|
||||
```typescript
|
||||
type CompassAbbr = 'N' | 'NE' | 'E' | 'SE' | 'S' | 'SW' | 'W' | 'NW';
|
||||
```
|
||||
|
||||
### `CompassName`
|
||||
|
||||
```typescript
|
||||
type CompassName =
|
||||
| 'North' | 'Northeast' | 'East' | 'Southeast'
|
||||
| 'South' | 'Southwest' | 'West' | 'Northwest';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[Home](Home) | [Architecture](Architecture)
|
||||
116
.wiki/Architecture.md
Normal file
116
.wiki/Architecture.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# 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.
|
||||
|
||||
---
|
||||
|
||||
[Home](Home) | [API Reference](API-Reference)
|
||||
55
.wiki/Home.md
Normal file
55
.wiki/Home.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# @acamarata/qibla
|
||||
|
||||
Qibla direction, great-circle path, and haversine distance. Pure math, zero dependencies.
|
||||
|
||||
## Pages
|
||||
|
||||
- [API Reference](API-Reference) — Full function and constant reference
|
||||
- [Architecture](Architecture) — Algorithm design, spherical trigonometry, implementation decisions
|
||||
|
||||
## What It Does
|
||||
|
||||
This library computes three things:
|
||||
|
||||
1. **Qibla bearing** — the initial compass bearing from any point on Earth to the Ka'bah in Mecca, using the forward azimuth formula from spherical trigonometry
|
||||
2. **Great-circle path** — a series of waypoints along the geodesic from origin to Ka'bah, suitable for rendering on a map
|
||||
3. **Haversine distance** — the surface distance between two coordinate pairs using the haversine formula
|
||||
|
||||
All calculations use a spherical Earth model (WGS-84 volumetric mean radius, 6,371 km). The Ka'bah coordinates (21.422511°N, 39.82615°E) are sourced from verified GPS data.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
npm install @acamarata/qibla
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { qiblaAngle, compassDir, distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||
|
||||
const bearing = qiblaAngle(40.7128, -74.006); // New York
|
||||
console.log(bearing); // ~58.48
|
||||
console.log(compassDir(bearing)); // "NE"
|
||||
|
||||
const km = distanceKm(40.7128, -74.006, KAABA_LAT, KAABA_LNG);
|
||||
console.log(km); // ~9,634
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @acamarata/qibla
|
||||
# or
|
||||
pnpm add @acamarata/qibla
|
||||
# or
|
||||
yarn add @acamarata/qibla
|
||||
```
|
||||
|
||||
## Related Packages
|
||||
|
||||
- [pray-calc](https://github.com/acamarata/pray-calc) — Islamic prayer times
|
||||
- [nrel-spa](https://github.com/acamarata/nrel-spa) — NREL Solar Position Algorithm
|
||||
- [moon-sighting](https://github.com/acamarata/moon-sighting) — Lunar crescent visibility
|
||||
|
||||
---
|
||||
|
||||
[API Reference](API-Reference) | [Architecture](Architecture)
|
||||
29
README.md
29
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# qibla
|
||||
# @acamarata/qibla
|
||||
|
||||
[](https://www.npmjs.com/package/qibla)
|
||||
[](https://www.npmjs.com/package/%40acamarata%2Fqibla)
|
||||
[](https://github.com/acamarata/qibla/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ Qibla direction, great-circle path, and haversine distance. Pure math, zero depe
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
npm install qibla
|
||||
npm install @acamarata/qibla
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
distanceKm,
|
||||
KAABA_LAT,
|
||||
KAABA_LNG,
|
||||
} from "qibla";
|
||||
} from "@acamarata/qibla";
|
||||
|
||||
// Bearing from New York to the Ka'bah
|
||||
const bearing = qiblaAngle(40.7128, -74.006);
|
||||
|
|
@ -73,6 +73,12 @@ Haversine distance between two points in kilometers (spherical Earth approximati
|
|||
| `KAABA_LNG` | 39.826150 | Ka'bah center longitude (degrees east) |
|
||||
| `EARTH_RADIUS_KM` | 6371 | WGS-84 volumetric mean radius |
|
||||
|
||||
## Architecture
|
||||
|
||||
All calculations use the forward azimuth formula from spherical trigonometry. Great-circle paths use Slerp (spherical linear interpolation). Distance uses the haversine formula. The Ka'bah coordinates are fixed constants from verified GPS data.
|
||||
|
||||
See [Architecture](https://github.com/acamarata/qibla/wiki/Architecture) for algorithm details.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Node.js 20+. Works in browsers and all major bundlers (Webpack, Vite, Rollup, esbuild). Ships as dual CJS/ESM with full TypeScript definitions.
|
||||
|
|
@ -80,15 +86,24 @@ Node.js 20+. Works in browsers and all major bundlers (Webpack, Vite, Rollup, es
|
|||
## TypeScript
|
||||
|
||||
```typescript
|
||||
import { qiblaAngle, CompassAbbr, CompassName } from "qibla";
|
||||
import { qiblaAngle, CompassAbbr, CompassName } from "@acamarata/qibla";
|
||||
|
||||
const bearing: number = qiblaAngle(40.7128, -74.006);
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Full reference available on the [GitHub Wiki](https://github.com/acamarata/qibla/wiki):
|
||||
|
||||
- [Home](https://github.com/acamarata/qibla/wiki/Home) — Overview and quick start
|
||||
- [API Reference](https://github.com/acamarata/qibla/wiki/API-Reference) — Full function and constant reference
|
||||
- [Architecture](https://github.com/acamarata/qibla/wiki/Architecture) — Algorithm design, spherical trigonometry, Slerp implementation
|
||||
|
||||
## Related
|
||||
|
||||
- [pray-calc](https://github.com/acamarata/pray-calc) - Islamic prayer times calculator
|
||||
- [nrel-spa](https://github.com/acamarata/nrel-spa) - NREL Solar Position Algorithm
|
||||
- [pray-calc](https://github.com/acamarata/pray-calc) — Islamic prayer times calculator
|
||||
- [nrel-spa](https://github.com/acamarata/nrel-spa) — NREL Solar Position Algorithm
|
||||
- [moon-sighting](https://github.com/acamarata/moon-sighting) — Lunar crescent visibility
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@
|
|||
"dist/index.cjs",
|
||||
"dist/index.mjs",
|
||||
"dist/index.d.ts",
|
||||
"dist/index.d.mts"
|
||||
"dist/index.d.mts",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default defineConfig({
|
|||
clean: true,
|
||||
outDir: "dist",
|
||||
splitting: false,
|
||||
sourcemap: false,
|
||||
sourcemap: true,
|
||||
target: "es2020",
|
||||
platform: "neutral",
|
||||
outExtension({ format }) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue