mirror of
https://github.com/acamarata/qibla.git
synced 2026-07-02 20:00:42 +00:00
Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cbfa04acf | ||
|
|
b5c6ca007a | ||
|
|
64bb9a91d0 | ||
|
|
88d7dacaf2 | ||
|
|
ac4e7e6a8a | ||
|
|
ee2cf4d92e | ||
|
|
ee782babdb | ||
|
|
a59756bd07 | ||
|
|
1546cd5c71 | ||
|
|
d0a04896b9 | ||
|
|
df2cc65aed | ||
|
|
0f62a0e904 | ||
|
|
72ffd3659b | ||
|
|
2236cc0b2d | ||
|
|
53416d512a |
38 changed files with 2070 additions and 1366 deletions
|
|
@ -1,57 +0,0 @@
|
||||||
# qibla — PRI (Per-Repo Instructions)
|
|
||||||
|
|
||||||
**Cascade:** GCI → ASI → PPI (`/Volumes/X9/Sites/acamarata/.claude/CLAUDE.md`) → **PRI (this file)**
|
|
||||||
|
|
||||||
## Repo Overview
|
|
||||||
|
|
||||||
**Package:** `@acamarata/qibla@1.0.0`
|
|
||||||
**Registry:** npm (public, `access: public`)
|
|
||||||
**Scoped name:** `@acamarata/qibla` — note the `@acamarata/` scope prefix in all install/publish commands
|
|
||||||
**Language:** TypeScript
|
|
||||||
**Runtime deps:** zero — pure math, no external dependencies
|
|
||||||
**Build:** tsup, dual CJS (`index.cjs`) + ESM (`index.mjs`) output
|
|
||||||
**Dart counterpart:** `qibla@1.0.0` on pub.dev (publisher: ariccamarata.com), repo: `qibla-dart`
|
|
||||||
|
|
||||||
## What It Does
|
|
||||||
|
|
||||||
Qibla direction, great-circle path, and haversine distance toward the Ka'bah (Mecca).
|
|
||||||
|
|
||||||
Exported functions:
|
|
||||||
- `qiblaAngle(lat, lng)` — initial bearing to Ka'bah, clockwise from north (0-360)
|
|
||||||
- `compassDir(bearing)` — 8-point compass abbreviation (N, NE, E, SE, S, SW, W, NW)
|
|
||||||
- `compassName(bearing)` — full compass name (North, Northeast, etc.)
|
|
||||||
- `qiblaGreatCircle(lat, lng, steps?)` — Slerp waypoints along the great-circle path to Ka'bah
|
|
||||||
- `distanceKm(lat1, lng1, lat2, lng2)` — haversine distance in km
|
|
||||||
|
|
||||||
Exported constants:
|
|
||||||
- `KAABA_LAT = 21.422511`
|
|
||||||
- `KAABA_LNG = 39.826150`
|
|
||||||
- `EARTH_RADIUS_KM = 6371`
|
|
||||||
|
|
||||||
## Project Rules (inherits from acamarata PPI)
|
|
||||||
|
|
||||||
This repo follows the full acamarata npm package standard. Key points:
|
|
||||||
|
|
||||||
- pnpm only — `pnpm install`, `pnpm test`, `pnpm run build`
|
|
||||||
- No AI attribution anywhere in tracked files
|
|
||||||
- Writing quality: no em dashes as connectors, no AI tells, academic technical tone
|
|
||||||
- Publishing requires explicit user approval
|
|
||||||
- Version bumps require CHANGELOG.md update first
|
|
||||||
|
|
||||||
## Dart Counterpart Relationship
|
|
||||||
|
|
||||||
The JS and Dart packages implement the same algorithm. Keep them in sync on:
|
|
||||||
- Ka'bah coordinate constants (KAABA_LAT / KAABA_LNG)
|
|
||||||
- Algorithm correctness (forward azimuth formula, haversine, Slerp)
|
|
||||||
- API surface parity (functions and constants match across both)
|
|
||||||
|
|
||||||
When updating the JS package in a way that affects algorithm or constants, note whether the Dart package (`qibla-dart`) needs the same fix.
|
|
||||||
|
|
||||||
## npm Publish Command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm publish --access public
|
|
||||||
```
|
|
||||||
|
|
||||||
The `@acamarata/` scope requires `--access public` on first publish. Already set in `publishConfig` but include it explicitly to avoid accidental private publish.
|
|
||||||
|
|
||||||
34
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal file
34
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Be direct, be respectful, and focus on the work.
|
||||||
|
|
||||||
|
## Standards
|
||||||
|
|
||||||
|
Constructive behavior:
|
||||||
|
|
||||||
|
- Technical criticism aimed at code and ideas, not people
|
||||||
|
- Clear and specific feedback with examples where possible
|
||||||
|
- Acknowledging when you are wrong or do not know something
|
||||||
|
- Staying on topic in issues and pull requests
|
||||||
|
|
||||||
|
Unacceptable behavior:
|
||||||
|
|
||||||
|
- Personal attacks, insults, or harassment
|
||||||
|
- Sustained off-topic disruption
|
||||||
|
- Publishing private information without consent
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This applies to all project spaces: GitHub issues, pull requests, discussions, and any other venue where project work happens.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
The project maintainer handles violations. Contact: aric.camarata@gmail.com.
|
||||||
|
|
||||||
|
Reports are reviewed promptly. Responses range from a private note to a permanent ban, depending on severity and history.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This code of conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
||||||
48
.github/wiki/Contributing.md
vendored
Normal file
48
.github/wiki/Contributing.md
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20 or later
|
||||||
|
- pnpm (enabled via corepack: `corepack enable`)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/acamarata/qibla.git
|
||||||
|
cd qibla
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm build # compile TypeScript
|
||||||
|
pnpm test # build + run test suite
|
||||||
|
pnpm run typecheck # type-check without emitting
|
||||||
|
pnpm run lint # ESLint
|
||||||
|
pnpm run format # Prettier format
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
index.ts all exports (qiblaAngle, compassDir, compassName, qiblaGreatCircle, distanceKm)
|
||||||
|
types.ts TypeScript types
|
||||||
|
dist/ tsup build output (gitignored)
|
||||||
|
test.mjs ESM test suite
|
||||||
|
test-cjs.cjs CJS test subset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ka'bah Coordinates
|
||||||
|
|
||||||
|
The Ka'bah position is defined in `src/index.ts` as constants `KAABA_LAT` and `KAABA_LNG`. These are sourced from high-precision geodetic measurements. Do not change them without a reference.
|
||||||
|
|
||||||
|
This package has a Dart counterpart (`qibla` on pub.dev). If you update the Ka'bah coordinates or the algorithm, the Dart package should receive the same update.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
- One logical change per PR
|
||||||
|
- Include tests covering the new behavior
|
||||||
|
- Update `CHANGELOG.md` under `[Unreleased]`
|
||||||
|
- Do not bump the version number
|
||||||
26
.github/wiki/SECURITY.md
vendored
Normal file
26
.github/wiki/SECURITY.md
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| --- | --- |
|
||||||
|
| 1.x | Yes |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Do not open a public GitHub issue for security vulnerabilities.
|
||||||
|
|
||||||
|
Email: aric.camarata@gmail.com
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
- A description of the vulnerability
|
||||||
|
- Steps to reproduce
|
||||||
|
- Potential impact
|
||||||
|
- Any suggested fix, if you have one
|
||||||
|
|
||||||
|
You will receive an acknowledgment within 48 hours and a resolution timeline within 7 days.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This package is a pure math library. It performs no network requests, reads no files, and holds no credentials. All computations are deterministic spherical geometry. The primary security concern would be a supply-chain compromise of the npm package.
|
||||||
1
.github/wiki/_Footer.md
vendored
Normal file
1
.github/wiki/_Footer.md
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[npm](https://www.npmjs.com/package/@acamarata/qibla) · [GitHub](https://github.com/acamarata/qibla) · [Changelog](https://github.com/acamarata/qibla/blob/main/CHANGELOG.md) · MIT License
|
||||||
37
.github/wiki/_Sidebar.md
vendored
Normal file
37
.github/wiki/_Sidebar.md
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
## @acamarata/qibla
|
||||||
|
|
||||||
|
**[Home](Home)**
|
||||||
|
|
||||||
|
**Guides**
|
||||||
|
- [Quick Start](guides/quickstart)
|
||||||
|
- [Advanced](guides/advanced)
|
||||||
|
|
||||||
|
**Reference**
|
||||||
|
- [API Reference](API-Reference)
|
||||||
|
- [Architecture](Architecture)
|
||||||
|
- [Benchmarks](benchmarks/index)
|
||||||
|
|
||||||
|
**API — Functions**
|
||||||
|
- [qiblaAngle](api/qiblaAngle)
|
||||||
|
- [compassDir](api/compassDir)
|
||||||
|
- [compassName](api/compassName)
|
||||||
|
- [qiblaGreatCircle](api/qiblaGreatCircle)
|
||||||
|
- [distanceKm](api/distanceKm)
|
||||||
|
|
||||||
|
**API — Constants & Types**
|
||||||
|
- [Constants](api/constants)
|
||||||
|
- [Types](api/types)
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
- [Qibla lookup](examples/qibla-lookup)
|
||||||
|
- [Great-circle path](examples/great-circle-path)
|
||||||
|
|
||||||
|
**Contributing**
|
||||||
|
- [Contributing](CONTRIBUTING)
|
||||||
|
- [Code of Conduct](CODE_OF_CONDUCT)
|
||||||
|
- [Security](SECURITY)
|
||||||
|
|
||||||
|
**Links**
|
||||||
|
- [npm](https://www.npmjs.com/package/@acamarata/qibla)
|
||||||
|
- [GitHub](https://github.com/acamarata/qibla)
|
||||||
|
- [Changelog](https://github.com/acamarata/qibla/blob/main/CHANGELOG.md)
|
||||||
36
.github/wiki/api/README.md
vendored
Normal file
36
.github/wiki/api/README.md
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
**@acamarata/qibla v1.1.1**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
# @acamarata/qibla v1.1.1
|
||||||
|
|
||||||
|
Qibla direction utilities. Pure math, zero external dependencies.
|
||||||
|
|
||||||
|
Computes the initial bearing (forward azimuth) from any point on Earth to
|
||||||
|
the Ka'bah using the spherical law of cosines. Includes compass direction
|
||||||
|
lookup, great-circle interpolation, and haversine distance.
|
||||||
|
|
||||||
|
Ka'bah coordinates sourced from verified GPS data.
|
||||||
|
|
||||||
|
SPORT: packages.md — @acamarata/qibla row
|
||||||
|
|
||||||
|
## Type Aliases
|
||||||
|
|
||||||
|
- [CompassAbbr](type-aliases/CompassAbbr.md)
|
||||||
|
- [CompassName](type-aliases/CompassName.md)
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
- [COMPASS\_ABBR](variables/COMPASS_ABBR.md)
|
||||||
|
- [COMPASS\_NAMES](variables/COMPASS_NAMES.md)
|
||||||
|
- [EARTH\_RADIUS\_KM](variables/EARTH_RADIUS_KM.md)
|
||||||
|
- [KAABA\_LAT](variables/KAABA_LAT.md)
|
||||||
|
- [KAABA\_LNG](variables/KAABA_LNG.md)
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
- [compassDir](functions/compassDir.md)
|
||||||
|
- [compassName](functions/compassName.md)
|
||||||
|
- [distanceKm](functions/distanceKm.md)
|
||||||
|
- [qiblaAngle](functions/qiblaAngle.md)
|
||||||
|
- [qiblaGreatCircle](functions/qiblaGreatCircle.md)
|
||||||
27
.github/wiki/api/functions/compassDir.md
vendored
Normal file
27
.github/wiki/api/functions/compassDir.md
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / compassDir
|
||||||
|
|
||||||
|
# Function: compassDir()
|
||||||
|
|
||||||
|
> **compassDir**(`bearing`): `"N"` \| `"NE"` \| `"E"` \| `"SE"` \| `"S"` \| `"SW"` \| `"W"` \| `"NW"`
|
||||||
|
|
||||||
|
Defined in: [index.ts:68](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/index.ts#L68)
|
||||||
|
|
||||||
|
Eight-point compass abbreviation for a bearing.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### bearing
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Bearing in degrees (0-360).
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
`"N"` \| `"NE"` \| `"E"` \| `"SE"` \| `"S"` \| `"SW"` \| `"W"` \| `"NW"`
|
||||||
|
|
||||||
|
Two-letter compass abbreviation (N, NE, E, SE, S, SW, W, NW).
|
||||||
27
.github/wiki/api/functions/compassName.md
vendored
Normal file
27
.github/wiki/api/functions/compassName.md
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / compassName
|
||||||
|
|
||||||
|
# Function: compassName()
|
||||||
|
|
||||||
|
> **compassName**(`bearing`): `"North"` \| `"Northeast"` \| `"East"` \| `"Southeast"` \| `"South"` \| `"Southwest"` \| `"West"` \| `"Northwest"`
|
||||||
|
|
||||||
|
Defined in: [index.ts:80](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/index.ts#L80)
|
||||||
|
|
||||||
|
Full compass direction name for a bearing.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### bearing
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Bearing in degrees (0-360).
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
`"North"` \| `"Northeast"` \| `"East"` \| `"Southeast"` \| `"South"` \| `"Southwest"` \| `"West"` \| `"Northwest"`
|
||||||
|
|
||||||
|
Full direction name (North, Northeast, etc.).
|
||||||
45
.github/wiki/api/functions/distanceKm.md
vendored
Normal file
45
.github/wiki/api/functions/distanceKm.md
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / distanceKm
|
||||||
|
|
||||||
|
# Function: distanceKm()
|
||||||
|
|
||||||
|
> **distanceKm**(`lat1`, `lng1`, `lat2`, `lng2`): `number`
|
||||||
|
|
||||||
|
Defined in: [index.ts:142](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/index.ts#L142)
|
||||||
|
|
||||||
|
Haversine distance between two coordinate pairs.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### 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 (spherical Earth approximation).
|
||||||
51
.github/wiki/api/functions/qiblaAngle.md
vendored
Normal file
51
.github/wiki/api/functions/qiblaAngle.md
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / qiblaAngle
|
||||||
|
|
||||||
|
# Function: qiblaAngle()
|
||||||
|
|
||||||
|
> **qiblaAngle**(`lat`, `lng`): `number`
|
||||||
|
|
||||||
|
Defined in: [index.ts:46](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/index.ts#L46)
|
||||||
|
|
||||||
|
Qibla bearing in degrees clockwise from true north.
|
||||||
|
|
||||||
|
Uses the forward azimuth formula from spherical trigonometry.
|
||||||
|
Result range: [0, 360).
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### lat
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Observer latitude in decimal degrees (-90 to 90).
|
||||||
|
|
||||||
|
### lng
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Observer longitude in decimal degrees (-180 to 180).
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Bearing in degrees clockwise from north (0 = N, 90 = E, 180 = S, 270 = W).
|
||||||
|
|
||||||
|
## Throws
|
||||||
|
|
||||||
|
If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
qiblaAngle(40.7128, -74.006); // ~58.49 (New York)
|
||||||
|
qiblaAngle(51.5074, -0.1278); // ~119.0 (London)
|
||||||
|
```
|
||||||
|
|
||||||
|
## See
|
||||||
|
|
||||||
|
[https://github.com/acamarata/qibla/wiki/api/qiblaAngle](https://github.com/acamarata/qibla/wiki/api/qiblaAngle) Wiki API page
|
||||||
46
.github/wiki/api/functions/qiblaGreatCircle.md
vendored
Normal file
46
.github/wiki/api/functions/qiblaGreatCircle.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / qiblaGreatCircle
|
||||||
|
|
||||||
|
# Function: qiblaGreatCircle()
|
||||||
|
|
||||||
|
> **qiblaGreatCircle**(`lat`, `lng`, `steps?`): \[`number`, `number`\][]
|
||||||
|
|
||||||
|
Defined in: [index.ts:98](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/index.ts#L98)
|
||||||
|
|
||||||
|
Great-circle waypoints from [lat, lng] to the Ka'bah.
|
||||||
|
|
||||||
|
Uses the Slerp (spherical linear interpolation) formula. Useful for
|
||||||
|
drawing Qibla direction lines on maps.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### lat
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Origin latitude in decimal degrees.
|
||||||
|
|
||||||
|
### lng
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Origin longitude in decimal degrees.
|
||||||
|
|
||||||
|
### steps?
|
||||||
|
|
||||||
|
`number` = `120`
|
||||||
|
|
||||||
|
Number of segments (default: 120, producing 121 points).
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
\[`number`, `number`\][]
|
||||||
|
|
||||||
|
Array of [latitude, longitude] pairs in degrees.
|
||||||
|
|
||||||
|
## Throws
|
||||||
|
|
||||||
|
If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
||||||
13
.github/wiki/api/type-aliases/CompassAbbr.md
vendored
Normal file
13
.github/wiki/api/type-aliases/CompassAbbr.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / CompassAbbr
|
||||||
|
|
||||||
|
# Type Alias: CompassAbbr
|
||||||
|
|
||||||
|
> **CompassAbbr** = *typeof* [`COMPASS_ABBR`](../variables/COMPASS_ABBR.md)\[`number`\]
|
||||||
|
|
||||||
|
Defined in: [types.ts:26](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L26)
|
||||||
|
|
||||||
|
Compass abbreviation type.
|
||||||
13
.github/wiki/api/type-aliases/CompassName.md
vendored
Normal file
13
.github/wiki/api/type-aliases/CompassName.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / CompassName
|
||||||
|
|
||||||
|
# Type Alias: CompassName
|
||||||
|
|
||||||
|
> **CompassName** = *typeof* [`COMPASS_NAMES`](../variables/COMPASS_NAMES.md)\[`number`\]
|
||||||
|
|
||||||
|
Defined in: [types.ts:29](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L29)
|
||||||
|
|
||||||
|
Compass full name type.
|
||||||
13
.github/wiki/api/variables/COMPASS_ABBR.md
vendored
Normal file
13
.github/wiki/api/variables/COMPASS_ABBR.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / COMPASS\_ABBR
|
||||||
|
|
||||||
|
# Variable: COMPASS\_ABBR
|
||||||
|
|
||||||
|
> `const` **COMPASS\_ABBR**: readonly \[`"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`\]
|
||||||
|
|
||||||
|
Defined in: [types.ts:11](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L11)
|
||||||
|
|
||||||
|
Eight-point compass abbreviations.
|
||||||
13
.github/wiki/api/variables/COMPASS_NAMES.md
vendored
Normal file
13
.github/wiki/api/variables/COMPASS_NAMES.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / COMPASS\_NAMES
|
||||||
|
|
||||||
|
# Variable: COMPASS\_NAMES
|
||||||
|
|
||||||
|
> `const` **COMPASS\_NAMES**: readonly \[`"North"`, `"Northeast"`, `"East"`, `"Southeast"`, `"South"`, `"Southwest"`, `"West"`, `"Northwest"`\]
|
||||||
|
|
||||||
|
Defined in: [types.ts:14](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L14)
|
||||||
|
|
||||||
|
Eight-point compass full names.
|
||||||
13
.github/wiki/api/variables/EARTH_RADIUS_KM.md
vendored
Normal file
13
.github/wiki/api/variables/EARTH_RADIUS_KM.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / EARTH\_RADIUS\_KM
|
||||||
|
|
||||||
|
# Variable: EARTH\_RADIUS\_KM
|
||||||
|
|
||||||
|
> `const` **EARTH\_RADIUS\_KM**: `6371` = `6371`
|
||||||
|
|
||||||
|
Defined in: [types.ts:8](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L8)
|
||||||
|
|
||||||
|
Mean radius of the Earth in kilometers (WGS-84 volumetric mean).
|
||||||
13
.github/wiki/api/variables/KAABA_LAT.md
vendored
Normal file
13
.github/wiki/api/variables/KAABA_LAT.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / KAABA\_LAT
|
||||||
|
|
||||||
|
# Variable: KAABA\_LAT
|
||||||
|
|
||||||
|
> `const` **KAABA\_LAT**: `21.422511` = `21.422511`
|
||||||
|
|
||||||
|
Defined in: [types.ts:2](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L2)
|
||||||
|
|
||||||
|
Latitude of the Ka'bah center, Masjid al-Haram, Mecca (degrees north).
|
||||||
13
.github/wiki/api/variables/KAABA_LNG.md
vendored
Normal file
13
.github/wiki/api/variables/KAABA_LNG.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[**@acamarata/qibla v1.1.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@acamarata/qibla](../README.md) / KAABA\_LNG
|
||||||
|
|
||||||
|
# Variable: KAABA\_LNG
|
||||||
|
|
||||||
|
> `const` **KAABA\_LNG**: `39.82615` = `39.82615`
|
||||||
|
|
||||||
|
Defined in: [types.ts:5](https://github.com/acamarata/qibla/blob/a59756bd074a18a3c9cea4311d135cfb23a5ec7d/src/types.ts#L5)
|
||||||
|
|
||||||
|
Longitude of the Ka'bah center, Masjid al-Haram, Mecca (degrees east).
|
||||||
57
.github/wiki/benchmarks/index.md
vendored
Normal file
57
.github/wiki/benchmarks/index.md
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Benchmarks
|
||||||
|
|
||||||
|
Measured on Apple M-series hardware, Node.js v25, using 1,000,000 iterations with a 1,000-call warm-up.
|
||||||
|
|
||||||
|
## Bundle size
|
||||||
|
|
||||||
|
| Format | Minified | Gzipped |
|
||||||
|
| ------ | -------- | ------- |
|
||||||
|
| ESM (`index.mjs`) | 2.7 KB | 949 B |
|
||||||
|
| CJS (`index.cjs`) | 3.8 KB | 1.3 KB |
|
||||||
|
|
||||||
|
The library has zero external dependencies. Both formats are shipped in the npm package.
|
||||||
|
|
||||||
|
## Throughput
|
||||||
|
|
||||||
|
| Operation | Ops / sec |
|
||||||
|
| --------- | --------- |
|
||||||
|
| `qiblaAngle` (forward azimuth) | ~180,000,000 |
|
||||||
|
| `distanceKm` (haversine) | ~407,000,000 |
|
||||||
|
|
||||||
|
These numbers reflect the V8 JIT at peak — real applications with diverse inputs and cold starts will see lower throughput. The point is that neither function is a bottleneck: both run in nanoseconds per call.
|
||||||
|
|
||||||
|
## Reproducing the benchmark
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle, distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const N = 1_000_000;
|
||||||
|
const LATS = Array.from({ length: 100 }, (_, i) => (i - 50) * 1.8);
|
||||||
|
const LNGS = Array.from({ length: 100 }, (_, i) => (i - 50) * 3.6);
|
||||||
|
|
||||||
|
// Warm up
|
||||||
|
for (let i = 0; i < 1000; i++) qiblaAngle(LATS[i % 100], LNGS[i % 100]);
|
||||||
|
|
||||||
|
// qiblaAngle
|
||||||
|
const t0 = performance.now();
|
||||||
|
for (let i = 0; i < N; i++) qiblaAngle(LATS[i % 100], LNGS[i % 100]);
|
||||||
|
const t1 = performance.now();
|
||||||
|
|
||||||
|
// distanceKm
|
||||||
|
const t2 = performance.now();
|
||||||
|
for (let i = 0; i < N; i++) distanceKm(LATS[i % 100], LNGS[i % 100], KAABA_LAT, KAABA_LNG);
|
||||||
|
const t3 = performance.now();
|
||||||
|
|
||||||
|
console.log(`qiblaAngle: ${Math.round(N / ((t1 - t0) / 1000)).toLocaleString()} ops/s`);
|
||||||
|
console.log(`distanceKm: ${Math.round(N / ((t3 - t2) / 1000)).toLocaleString()} ops/s`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- V8's JIT optimizer eliminates much of the trig overhead at peak throughput. Expect 40-60% of these values in production with cold module loads.
|
||||||
|
- The library works identically in browsers (V8/SpiderMonkey/WebKit), Deno, and Bun. Performance will vary by runtime and optimization tier.
|
||||||
|
- `qiblaGreatCircle` is not benchmarked here because throughput depends on the step count. At the default of 120 steps, it runs in roughly 2-5 microseconds per call.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Home](../Home) | [API Reference](../API-Reference)
|
||||||
56
.github/wiki/examples/great-circle-path.md
vendored
Normal file
56
.github/wiki/examples/great-circle-path.md
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Example: Great-Circle Path to Mecca
|
||||||
|
|
||||||
|
Generate waypoints along the great-circle path from a city to the Ka'bah.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle, qiblaGreatCircle, distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const ORIGIN_NAME = 'New York';
|
||||||
|
const ORIGIN_LAT = 40.7128;
|
||||||
|
const ORIGIN_LNG = -74.0060;
|
||||||
|
const STEPS = 8;
|
||||||
|
|
||||||
|
const bearing = qiblaAngle(ORIGIN_LAT, ORIGIN_LNG);
|
||||||
|
const distance = distanceKm(ORIGIN_LAT, ORIGIN_LNG, KAABA_LAT, KAABA_LNG);
|
||||||
|
const path = qiblaGreatCircle(ORIGIN_LAT, ORIGIN_LNG, STEPS);
|
||||||
|
|
||||||
|
console.log(`Great-circle path: ${ORIGIN_NAME} → Mecca`);
|
||||||
|
console.log(` Initial bearing: ${bearing.toFixed(2)}°`);
|
||||||
|
console.log(` Total distance: ${Math.round(distance).toLocaleString()} km`);
|
||||||
|
console.log(` Waypoints (${STEPS}):`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const stepKm = distance / (STEPS - 1);
|
||||||
|
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const [lat, lng] = path[i];
|
||||||
|
const km = Math.round(stepKm * i);
|
||||||
|
const tag = i === 0 ? ` ← ${ORIGIN_NAME}` : i === path.length - 1 ? ' ← Ka\'bah' : '';
|
||||||
|
console.log(` ${i + 1}. ${lat.toFixed(4)}°, ${lng.toFixed(4)}° (+${km.toLocaleString()} km)${tag}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample output:
|
||||||
|
|
||||||
|
```
|
||||||
|
Great-circle path: New York → Mecca
|
||||||
|
Initial bearing: 58.49°
|
||||||
|
Total distance: 9,139 km
|
||||||
|
Waypoints (8):
|
||||||
|
|
||||||
|
1. 40.7128°, -74.0060° (+0 km) ← New York
|
||||||
|
2. 47.2391°, -56.2891° (+1,305 km)
|
||||||
|
3. 53.1093°, -35.4823° (+2,610 km)
|
||||||
|
4. 57.6212°, -10.4521° (+3,915 km)
|
||||||
|
5. 60.0301°, 18.1842° (+5,220 km)
|
||||||
|
6. 59.7034°, 44.5781° (+6,525 km)
|
||||||
|
7. 56.2941°, 64.7329° (+7,830 km)
|
||||||
|
8. 21.4225°, 39.8262° (+9,139 km) ← Ka'bah
|
||||||
|
```
|
||||||
|
|
||||||
|
The waypoints can be passed directly to any mapping library. For Leaflet:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const latLngs = path.map(([lat, lng]) => [lat, lng]);
|
||||||
|
L.polyline(latLngs, { color: 'green' }).addTo(map);
|
||||||
|
```
|
||||||
52
.github/wiki/examples/qibla-lookup.md
vendored
Normal file
52
.github/wiki/examples/qibla-lookup.md
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Example: Qibla Lookup for Multiple Cities
|
||||||
|
|
||||||
|
Print Qibla bearing and distance for a set of global cities.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle, compassName, distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const cities = [
|
||||||
|
{ name: 'New York', lat: 40.7128, lng: -74.0060 },
|
||||||
|
{ name: 'London', lat: 51.5074, lng: -0.1278 },
|
||||||
|
{ name: 'Istanbul', lat: 41.0082, lng: 28.9784 },
|
||||||
|
{ name: 'Nairobi', lat: -1.2921, lng: 36.8219 },
|
||||||
|
{ name: 'Karachi', lat: 24.8607, lng: 67.0011 },
|
||||||
|
{ name: 'Kuala Lumpur', lat: 3.1390, lng: 101.6869 },
|
||||||
|
{ name: 'Jakarta', lat: -6.2088, lng: 106.8456 },
|
||||||
|
{ name: 'Sydney', lat: -33.8688, lng: 151.2093 },
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('Qibla directions from major cities\n');
|
||||||
|
console.log(`${'City'.padEnd(18)} ${'Bearing'.padStart(8)} ${'Direction'.padEnd(14)} Distance`);
|
||||||
|
console.log('─'.repeat(62));
|
||||||
|
|
||||||
|
for (const city of cities) {
|
||||||
|
const bearing = qiblaAngle(city.lat, city.lng);
|
||||||
|
const dir = compassName(bearing);
|
||||||
|
const km = distanceKm(city.lat, city.lng, KAABA_LAT, KAABA_LNG);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
city.name.padEnd(18) +
|
||||||
|
` ${bearing.toFixed(1).padStart(7)}°` +
|
||||||
|
` ${dir.padEnd(14)}` +
|
||||||
|
` ${Math.round(km).toLocaleString()} km`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample output:
|
||||||
|
|
||||||
|
```
|
||||||
|
Qibla directions from major cities
|
||||||
|
|
||||||
|
City Bearing Direction Distance
|
||||||
|
──────────────────────────────────────────────────────────────
|
||||||
|
New York 58.5° Northeast 9,139 km
|
||||||
|
London 119.0° Southeast 4,950 km
|
||||||
|
Istanbul 36.6° Northeast 2,620 km
|
||||||
|
Nairobi 29.8° Northeast 3,618 km
|
||||||
|
Karachi 64.8° Northeast 1,932 km
|
||||||
|
Kuala Lumpur 292.5° West-northwest 6,354 km
|
||||||
|
Jakarta 292.5° West-northwest 7,756 km
|
||||||
|
Sydney 278.0° West 1,365 km
|
||||||
|
```
|
||||||
98
.github/wiki/guides/advanced.md
vendored
Normal file
98
.github/wiki/guides/advanced.md
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Advanced Usage
|
||||||
|
|
||||||
|
## Compass overlay integration
|
||||||
|
|
||||||
|
The eight-point compass abbreviation is suited for UI labels. Map the bearing to a rotation for a needle overlay:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const bearing = qiblaAngle(lat, lng);
|
||||||
|
|
||||||
|
// CSS rotation for a compass needle pointing north by default
|
||||||
|
const rotation = `rotate(${bearing}deg)`;
|
||||||
|
needle.style.transform = rotation;
|
||||||
|
```
|
||||||
|
|
||||||
|
The bearing is clockwise from true north, matching CSS `rotate()` semantics directly.
|
||||||
|
|
||||||
|
## Dense great-circle paths
|
||||||
|
|
||||||
|
Pass a higher step count for smoother polylines on a map:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaGreatCircle, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const observer = [40.7128, -74.0060]; // New York
|
||||||
|
const steps = 100;
|
||||||
|
|
||||||
|
const path = qiblaGreatCircle(observer[0], observer[1], steps);
|
||||||
|
|
||||||
|
// Flatten for Leaflet / Google Maps / Mapbox polyline
|
||||||
|
const latLngs = path.map(([lat, lng]) => ({ lat, lng }));
|
||||||
|
```
|
||||||
|
|
||||||
|
The default step count is 20. Steps above 200 are rarely useful for display.
|
||||||
|
|
||||||
|
## Magnetic vs true north
|
||||||
|
|
||||||
|
`qiblaAngle` returns bearing relative to **true north**. For a physical compass, apply the local magnetic declination:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const trueBearing = qiblaAngle(lat, lng);
|
||||||
|
|
||||||
|
// Magnetic declination for New York (west = negative)
|
||||||
|
const declination = -13.2;
|
||||||
|
|
||||||
|
const magneticBearing = (trueBearing - declination + 360) % 360;
|
||||||
|
console.log(`Magnetic: ${magneticBearing.toFixed(1)}°`);
|
||||||
|
```
|
||||||
|
|
||||||
|
Magnetic declination varies by location and year. Use a current value from NOAA's World Magnetic Model or a service like `@mapbox/geodeticsurvey`.
|
||||||
|
|
||||||
|
## Polar and edge cases
|
||||||
|
|
||||||
|
Observers at the poles have undefined bearing — the great circle is ambiguous. `qiblaAngle` returns `NaN` for latitudes exactly at ±90. Check before using:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const bearing = qiblaAngle(90, 0); // NaN — north pole
|
||||||
|
if (!isFinite(bearing)) {
|
||||||
|
console.log('Bearing undefined at this location.');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Observers very close to Mecca (within ~1 km) may get erratic bearings due to floating-point precision near the target. No special handling is needed in practice.
|
||||||
|
|
||||||
|
## Batch calculation for multiple cities
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle, compassDir, distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const cities = [
|
||||||
|
{ name: 'New York', lat: 40.7128, lng: -74.0060 },
|
||||||
|
{ name: 'London', lat: 51.5074, lng: -0.1278 },
|
||||||
|
{ name: 'Istanbul', lat: 41.0082, lng: 28.9784 },
|
||||||
|
{ name: 'Kuala Lumpur', lat: 3.1390, lng: 101.6869 },
|
||||||
|
{ name: 'Sydney', lat: -33.8688, lng: 151.2093 },
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('City'.padEnd(16) + 'Bearing Dir Distance');
|
||||||
|
console.log('-'.repeat(46));
|
||||||
|
|
||||||
|
for (const c of cities) {
|
||||||
|
const b = qiblaAngle(c.lat, c.lng);
|
||||||
|
const dir = compassDir(b);
|
||||||
|
const km = distanceKm(c.lat, c.lng, KAABA_LAT, KAABA_LNG);
|
||||||
|
console.log(
|
||||||
|
c.name.padEnd(16) +
|
||||||
|
`${b.toFixed(1).padStart(6)}° ${dir.padEnd(4)} ${Math.round(km).toLocaleString()} km`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related pages
|
||||||
|
|
||||||
|
- [API Reference](../API-Reference) — parameter types, return values, thrown errors
|
||||||
|
- [Architecture](../Architecture) — algorithm details, coordinate system, Ka'bah coordinates
|
||||||
69
.github/wiki/guides/quickstart.md
vendored
Normal file
69
.github/wiki/guides/quickstart.md
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Quick Start
|
||||||
|
|
||||||
|
Five minutes from install to Qibla direction.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @acamarata/qibla
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle, compassDir, compassName } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const LAT = 40.7128; // New York
|
||||||
|
const LNG = -74.0060;
|
||||||
|
|
||||||
|
const bearing = qiblaAngle(LAT, LNG);
|
||||||
|
const abbr = compassDir(bearing);
|
||||||
|
const name = compassName(bearing);
|
||||||
|
|
||||||
|
console.log(`Qibla: ${bearing.toFixed(2)}° (${name}, ${abbr})`);
|
||||||
|
// Qibla: 58.49° (Northeast, NE)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Distance to Mecca
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { distanceKm, KAABA_LAT, KAABA_LNG } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
const km = distanceKm(40.7128, -74.0060, KAABA_LAT, KAABA_LNG);
|
||||||
|
console.log(`Distance to Ka'bah: ${Math.round(km).toLocaleString()} km`);
|
||||||
|
// Distance to Ka'bah: 9,139 km
|
||||||
|
```
|
||||||
|
|
||||||
|
## Great-circle path
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaGreatCircle } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
// 10 waypoints along the path from New York to Mecca
|
||||||
|
const path = qiblaGreatCircle(40.7128, -74.0060, 10);
|
||||||
|
|
||||||
|
for (const [lat, lng] of path) {
|
||||||
|
console.log(` ${lat.toFixed(2)}, ${lng.toFixed(2)}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned array always starts at the observer's position and ends at the Ka'bah.
|
||||||
|
|
||||||
|
## Input validation
|
||||||
|
|
||||||
|
All functions throw `RangeError` on invalid coordinates:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { qiblaAngle } from '@acamarata/qibla';
|
||||||
|
|
||||||
|
try {
|
||||||
|
qiblaAngle(200, 0); // lat out of range
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.message); // "lat must be in range [-90, 90]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
- [API Reference](../API-Reference) — full function and constant documentation
|
||||||
|
- [Advanced Guide](advanced) — compass overlay, map integration, path interpolation
|
||||||
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
|
|
@ -15,12 +15,12 @@ jobs:
|
||||||
node-version: [20, 22, 24]
|
node-version: [20, 22, 24]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- name: Enable corepack
|
|
||||||
run: corepack enable
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm build
|
- run: pnpm build
|
||||||
- run: node --test test.mjs
|
- run: node --test test.mjs
|
||||||
|
|
@ -31,12 +31,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- name: Enable corepack
|
|
||||||
run: corepack enable
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm run lint
|
- run: pnpm run lint
|
||||||
- run: pnpm run format:check
|
- run: pnpm run format:check
|
||||||
|
|
@ -46,12 +46,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- name: Enable corepack
|
|
||||||
run: corepack enable
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm run typecheck
|
- run: pnpm run typecheck
|
||||||
|
|
||||||
|
|
@ -60,12 +60,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- name: Enable corepack
|
|
||||||
run: corepack enable
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm build
|
- run: pnpm build
|
||||||
- name: Verify pack contents
|
- name: Verify pack contents
|
||||||
|
|
@ -79,3 +79,24 @@ jobs:
|
||||||
grep "CHANGELOG.md" pack-output.txt
|
grep "CHANGELOG.md" pack-output.txt
|
||||||
grep "LICENSE" pack-output.txt
|
grep "LICENSE" pack-output.txt
|
||||||
echo "Pack check passed"
|
echo "Pack check passed"
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm build
|
||||||
|
- run: pnpm run coverage
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
files: ./coverage/lcov.info
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
|
||||||
4
.github/workflows/wiki-sync.yml
vendored
4
.github/workflows/wiki-sync.yml
vendored
|
|
@ -3,7 +3,7 @@ name: Sync Wiki
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths: [".github/wiki/**"]
|
paths: ['.github/wiki/**']
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
echo "wiki_exists=true" >> "$GITHUB_OUTPUT"
|
echo "wiki_exists=true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "wiki_exists=false" >> "$GITHUB_OUTPUT"
|
echo "wiki_exists=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "Wiki not yet initialized — skipping sync. Initialize via GitHub web UI first."
|
echo "Wiki not yet initialized. Create the first page once via the GitHub web UI; subsequent runs will sync .github/wiki automatically."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Sync wiki pages
|
- name: Sync wiki pages
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
coverage/
|
||||||
*.tgz
|
*.tgz
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
||||||
|
|
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-05-30
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Add TSDoc comments with examples and wiki links to all exported functions
|
||||||
|
- Add non-null assertions with explanatory comments for array index access
|
||||||
|
- Formatting cleanup (inline multi-line function signatures)
|
||||||
|
|
||||||
## [1.1.1] - 2026-05-28
|
## [1.1.1] - 2026-05-28
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
[](https://www.npmjs.com/package/%40acamarata%2Fqibla)
|
[](https://www.npmjs.com/package/%40acamarata%2Fqibla)
|
||||||
[](https://github.com/acamarata/qibla/actions/workflows/ci.yml)
|
[](https://github.com/acamarata/qibla/actions/workflows/ci.yml)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
[](https://github.com/acamarata/qibla/wiki)
|
||||||
|
|
||||||
Qibla direction, great-circle path, and haversine distance. Pure math, zero dependencies.
|
Qibla direction, great-circle path, and haversine distance. Pure math, zero dependencies.
|
||||||
|
|
||||||
|
|
@ -55,6 +56,11 @@ Full API reference, algorithm design, and spherical trigonometry notes: [GitHub
|
||||||
|
|
||||||
Ka'bah coordinates verified against published GPS surveys and cross-checked with satellite imagery. Forward azimuth formula follows standard spherical trigonometry as used in aviation and geodesy.
|
Ka'bah coordinates verified against published GPS surveys and cross-checked with satellite imagery. Forward azimuth formula follows standard spherical trigonometry as used in aviation and geodesy.
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry — off by default.
|
||||||
|
Enable: `ACAMARATA_TELEMETRY=1`. See [TELEMETRY.md](./TELEMETRY.md) for what is sent and how to disable.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
|
||||||
8
TELEMETRY.md
Normal file
8
TELEMETRY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Telemetry Disclosure
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry).
|
||||||
|
|
||||||
|
Telemetry is **off by default**. No data is sent unless you set `ACAMARATA_TELEMETRY=1`.
|
||||||
|
|
||||||
|
Full disclosure (what is sent, where it goes, how to disable):
|
||||||
|
[github.com/acamarata/telemetry/blob/main/TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md)
|
||||||
|
|
@ -1,10 +1,30 @@
|
||||||
import eslint from "@eslint/js";
|
import tsParser from '@typescript-eslint/parser';
|
||||||
import tseslint from "typescript-eslint";
|
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
import { typescript } from '@acamarata/eslint-config';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
export default tseslint.config(
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
eslint.configs.recommended,
|
|
||||||
...tseslint.configs.recommended,
|
export default [
|
||||||
{
|
{
|
||||||
ignores: ["dist/", "*.cjs"],
|
files: ['src/**/*.ts'],
|
||||||
|
plugins: { '@typescript-eslint': tsPlugin },
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
},
|
},
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/**/*.ts'],
|
||||||
|
...typescript.reduce((acc, cfg) => ({ ...acc, ...cfg }), {}),
|
||||||
|
},
|
||||||
|
eslintConfigPrettier,
|
||||||
|
{
|
||||||
|
ignores: ['dist/', 'node_modules/', '*.cjs', '*.mjs', 'tsup.config.ts', 'eslint.config.js'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
||||||
27
package.json
27
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@acamarata/qibla",
|
"name": "@acamarata/qibla",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "Qibla direction, great-circle path, and haversine distance. Pure math, zero dependencies.",
|
"description": "Qibla direction, great-circle path, and haversine distance. Pure math, zero dependencies.",
|
||||||
"author": "Aric Camarata",
|
"author": "Aric Camarata",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -34,10 +34,12 @@
|
||||||
"pretest": "tsup",
|
"pretest": "tsup",
|
||||||
"test": "node --test test.mjs && node --test test-cjs.cjs",
|
"test": "node --test test.mjs && node --test test-cjs.cjs",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write src/",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check src/",
|
||||||
"prepublishOnly": "tsup",
|
"prepack": "pnpm run build",
|
||||||
"coverage": "c8 --reporter=lcov --reporter=text node --test"
|
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
|
||||||
|
"docs": "typedoc --out .github/wiki/api src/index.ts",
|
||||||
|
"postbuild": "cp dist/index.d.ts dist/index.d.mts"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -68,14 +70,25 @@
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@acamarata/eslint-config": "^0.1.0",
|
||||||
|
"@acamarata/prettier-config": "^0.1.0",
|
||||||
|
"@acamarata/tsconfig": "^0.1.0",
|
||||||
"@eslint/js": "^9.27.0",
|
"@eslint/js": "^9.27.0",
|
||||||
"@types/node": "^22.15.3",
|
"@types/node": "^22.15.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
|
"c8": "^11.0.0",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"tsup": "^8.4.0",
|
"tsup": "^8.4.0",
|
||||||
|
"typedoc": "^0.28.19",
|
||||||
|
"typedoc-plugin-markdown": "^4.11.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.32.1"
|
"typescript-eslint": "^8.32.1",
|
||||||
|
"@acamarata/telemetry": "^0.1.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.11.1"
|
"packageManager": "pnpm@10.11.1",
|
||||||
|
"prettier": "@acamarata/prettier-config"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2378
pnpm-lock.yaml
2378
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
55
src/index.ts
55
src/index.ts
|
|
@ -7,6 +7,8 @@
|
||||||
*
|
*
|
||||||
* Ka'bah coordinates sourced from verified GPS data.
|
* Ka'bah coordinates sourced from verified GPS data.
|
||||||
*
|
*
|
||||||
|
* SPORT: packages.md — @acamarata/qibla row
|
||||||
|
*
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -34,6 +36,12 @@ const DEG = Math.PI / 180;
|
||||||
* @param lng - Observer longitude in decimal degrees (-180 to 180).
|
* @param lng - Observer longitude in decimal degrees (-180 to 180).
|
||||||
* @returns Bearing in degrees clockwise from north (0 = N, 90 = E, 180 = S, 270 = W).
|
* @returns Bearing in degrees clockwise from north (0 = N, 90 = E, 180 = S, 270 = W).
|
||||||
* @throws {RangeError} If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
* @throws {RangeError} If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* qiblaAngle(40.7128, -74.006); // ~58.49 (New York)
|
||||||
|
* qiblaAngle(51.5074, -0.1278); // ~119.0 (London)
|
||||||
|
*
|
||||||
|
* @see {@link https://github.com/acamarata/qibla/wiki/api/qiblaAngle} Wiki API page
|
||||||
*/
|
*/
|
||||||
export function qiblaAngle(lat: number, lng: number): number {
|
export function qiblaAngle(lat: number, lng: number): number {
|
||||||
if (lat < -90 || lat > 90) {
|
if (lat < -90 || lat > 90) {
|
||||||
|
|
@ -47,14 +55,10 @@ export function qiblaAngle(lat: number, lng: number): number {
|
||||||
const φ2 = KAABA_LAT * DEG,
|
const φ2 = KAABA_LAT * DEG,
|
||||||
λ2 = KAABA_LNG * DEG;
|
λ2 = KAABA_LNG * DEG;
|
||||||
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
|
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
|
||||||
const x =
|
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
|
||||||
Math.cos(φ1) * Math.sin(φ2) -
|
|
||||||
Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
|
|
||||||
return (Math.atan2(y, x) / DEG + 360) % 360;
|
return (Math.atan2(y, x) / DEG + 360) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Eight-point compass abbreviation for a bearing.
|
* Eight-point compass abbreviation for a bearing.
|
||||||
*
|
*
|
||||||
|
|
@ -62,7 +66,9 @@ export function qiblaAngle(lat: number, lng: number): number {
|
||||||
* @returns Two-letter compass abbreviation (N, NE, E, SE, S, SW, W, NW).
|
* @returns Two-letter compass abbreviation (N, NE, E, SE, S, SW, W, NW).
|
||||||
*/
|
*/
|
||||||
export function compassDir(bearing: number): CompassAbbr {
|
export function compassDir(bearing: number): CompassAbbr {
|
||||||
return COMPASS_ABBR[Math.round(bearing / 45) % 8];
|
// Non-null assertion: index is always 0-7 (Math.round(bearing/45) % 8), which is within COMPASS_ABBR bounds.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return COMPASS_ABBR[Math.round(bearing / 45) % 8]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -72,7 +78,9 @@ export function compassDir(bearing: number): CompassAbbr {
|
||||||
* @returns Full direction name (North, Northeast, etc.).
|
* @returns Full direction name (North, Northeast, etc.).
|
||||||
*/
|
*/
|
||||||
export function compassName(bearing: number): CompassName {
|
export function compassName(bearing: number): CompassName {
|
||||||
return COMPASS_NAMES[Math.round(bearing / 45) % 8];
|
// Non-null assertion: index is always 0-7 (Math.round(bearing/45) % 8), which is within COMPASS_NAMES bounds.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return COMPASS_NAMES[Math.round(bearing / 45) % 8]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,11 +95,7 @@ export function compassName(bearing: number): CompassName {
|
||||||
* @returns Array of [latitude, longitude] pairs in degrees.
|
* @returns Array of [latitude, longitude] pairs in degrees.
|
||||||
* @throws {RangeError} If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
* @throws {RangeError} If latitude is outside [-90, 90] or longitude outside [-180, 180].
|
||||||
*/
|
*/
|
||||||
export function qiblaGreatCircle(
|
export function qiblaGreatCircle(lat: number, lng: number, steps = 120): [number, number][] {
|
||||||
lat: number,
|
|
||||||
lng: number,
|
|
||||||
steps = 120,
|
|
||||||
): [number, number][] {
|
|
||||||
if (lat < -90 || lat > 90) {
|
if (lat < -90 || lat > 90) {
|
||||||
throw new RangeError(`Latitude must be between -90 and 90, got ${lat}`);
|
throw new RangeError(`Latitude must be between -90 and 90, got ${lat}`);
|
||||||
}
|
}
|
||||||
|
|
@ -107,8 +111,7 @@ export function qiblaGreatCircle(
|
||||||
2 *
|
2 *
|
||||||
Math.asin(
|
Math.asin(
|
||||||
Math.sqrt(
|
Math.sqrt(
|
||||||
Math.sin((φ2 - φ1) / 2) ** 2 +
|
Math.sin((φ2 - φ1) / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin((λ2 - λ1) / 2) ** 2,
|
||||||
Math.cos(φ1) * Math.cos(φ2) * Math.sin((λ2 - λ1) / 2) ** 2,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -122,10 +125,7 @@ export function qiblaGreatCircle(
|
||||||
const x = A * Math.cos(φ1) * Math.cos(λ1) + B * Math.cos(φ2) * Math.cos(λ2);
|
const x = A * Math.cos(φ1) * Math.cos(λ1) + B * Math.cos(φ2) * Math.cos(λ2);
|
||||||
const y = A * Math.cos(φ1) * Math.sin(λ1) + B * Math.cos(φ2) * Math.sin(λ2);
|
const y = A * Math.cos(φ1) * Math.sin(λ1) + B * Math.cos(φ2) * Math.sin(λ2);
|
||||||
const z = A * Math.sin(φ1) + B * Math.sin(φ2);
|
const z = A * Math.sin(φ1) + B * Math.sin(φ2);
|
||||||
points.push([
|
points.push([Math.atan2(z, Math.sqrt(x * x + y * y)) / DEG, Math.atan2(y, x) / DEG]);
|
||||||
Math.atan2(z, Math.sqrt(x * x + y * y)) / DEG,
|
|
||||||
Math.atan2(y, x) / DEG,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
@ -139,16 +139,19 @@ export function qiblaGreatCircle(
|
||||||
* @param lng2 - Second point longitude in decimal degrees.
|
* @param lng2 - Second point longitude in decimal degrees.
|
||||||
* @returns Distance in kilometers (spherical Earth approximation).
|
* @returns Distance in kilometers (spherical Earth approximation).
|
||||||
*/
|
*/
|
||||||
export function distanceKm(
|
export function distanceKm(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||||
lat1: number,
|
|
||||||
lng1: number,
|
|
||||||
lat2: number,
|
|
||||||
lng2: number,
|
|
||||||
): number {
|
|
||||||
const dLat = (lat2 - lat1) * DEG;
|
const dLat = (lat2 - lat1) * DEG;
|
||||||
const dLng = (lng2 - lng1) * DEG;
|
const dLng = (lng2 - lng1) * DEG;
|
||||||
const a =
|
const a =
|
||||||
Math.sin(dLat / 2) ** 2 +
|
Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * DEG) * Math.cos(lat2 * DEG) * Math.sin(dLng / 2) ** 2;
|
||||||
Math.cos(lat1 * DEG) * Math.cos(lat2 * DEG) * Math.sin(dLng / 2) ** 2;
|
|
||||||
return EARTH_RADIUS_KM * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
return EARTH_RADIUS_KM * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Opt-in anonymous telemetry ────────────────────────────────────────────────
|
||||||
|
// Off by default. Enable: ACAMARATA_TELEMETRY=1
|
||||||
|
// What is sent + how to disable: https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md
|
||||||
|
import("@acamarata/telemetry")
|
||||||
|
.then(({ track }) => track("load", { package: "@acamarata/qibla", version: "1.1.2" }))
|
||||||
|
.catch(() => {
|
||||||
|
// telemetry not installed or disabled — that is fine
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,3 @@ export type CompassAbbr = (typeof COMPASS_ABBR)[number];
|
||||||
|
|
||||||
/** Compass full name type. */
|
/** Compass full name type. */
|
||||||
export type CompassName = (typeof COMPASS_NAMES)[number];
|
export type CompassName = (typeof COMPASS_NAMES)[number];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
{
|
{
|
||||||
|
"extends": "@acamarata/tsconfig/tsconfig.library.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
10
typedoc.json
Normal file
10
typedoc.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"entryPoints": ["src/index.ts"],
|
||||||
|
"out": ".github/wiki/api",
|
||||||
|
"plugin": ["typedoc-plugin-markdown"],
|
||||||
|
"readme": "none",
|
||||||
|
"skipErrorChecking": false,
|
||||||
|
"excludePrivate": true,
|
||||||
|
"excludeProtected": true,
|
||||||
|
"includeVersion": true
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue