mirror of
https://github.com/acamarata/nrel-spa.git
synced 2026-07-02 11:50:39 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74b6f609f8 | ||
|
|
35128dda86 | ||
|
|
70da984a3b | ||
|
|
1e75709789 | ||
|
|
6be2c20113 | ||
|
|
b52802f94b | ||
|
|
dea28b9262 | ||
|
|
8a8efd69ad | ||
|
|
a2a4564731 | ||
|
|
37f2823e9d |
36 changed files with 2089 additions and 79 deletions
|
|
@ -1 +0,0 @@
|
|||
CLAUDE.md
|
||||
165
.forgejo/workflows/ci.yml
Normal file
165
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# Forgejo CI mirror — git.ariccamarata.com
|
||||
# Mirrors .github/workflows/ci.yml for the self-hosted Forgejo Actions runner.
|
||||
# Keep in sync with the GitHub workflow; only addition is the nSentry failure step.
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22, 24]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: pnpm
|
||||
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build TypeScript
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run tests (ESM)
|
||||
run: node --test test.mjs
|
||||
|
||||
- name: Run tests (CJS)
|
||||
run: node --test test-cjs.cjs
|
||||
|
||||
- name: Report failure to nSentry
|
||||
if: failure()
|
||||
run: |
|
||||
# nself sentry ci enable <repo> must be run on the CamClaw server first.
|
||||
# Once registered, the runner's nself-sentry-sync hook delivers this report
|
||||
# to ~/Sites/acamarata/.claude/inbox via root@sentry-errors.ariccamarata.com.
|
||||
echo "CI_FAILURE repo=${{ github.repository }} job=${{ github.job }} run=${{ github.run_id }}" >&2
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
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 run typecheck
|
||||
|
||||
- name: Report failure to nSentry
|
||||
if: failure()
|
||||
run: |
|
||||
# nself sentry ci enable <repo> must be run on the CamClaw server first.
|
||||
# Once registered, the runner's nself-sentry-sync hook delivers this report
|
||||
# to ~/Sites/acamarata/.claude/inbox via root@sentry-errors.ariccamarata.com.
|
||||
echo "CI_FAILURE repo=${{ github.repository }} job=${{ github.job }} run=${{ github.run_id }}" >&2
|
||||
|
||||
lint:
|
||||
name: Lint & Format
|
||||
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 run lint
|
||||
- run: pnpm run format:check
|
||||
- name: Report failure to nSentry
|
||||
if: failure()
|
||||
run: |
|
||||
# nself sentry ci enable <repo> must be run on the CamClaw server first.
|
||||
# Once registered, the runner's nself-sentry-sync hook delivers this report
|
||||
# to ~/Sites/acamarata/.claude/inbox via root@sentry-errors.ariccamarata.com.
|
||||
echo "CI_FAILURE repo=${{ github.repository }} job=${{ github.job }} run=${{ github.run_id }}" >&2
|
||||
|
||||
pack-check:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
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 run build
|
||||
|
||||
- name: Verify package contents
|
||||
run: |
|
||||
npm pack --dry-run 2>&1 | tee pack-output.txt
|
||||
for f in dist/index.cjs dist/index.mjs dist/index.d.ts dist/index.d.mts lib/spa.js README.md CHANGELOG.md LICENSE; do
|
||||
grep -qE "(^|[[:space:]])${f}([[:space:]]|$)" pack-output.txt || { echo "MISSING: $f"; exit 1; }
|
||||
done
|
||||
echo "All expected files present in package"
|
||||
|
||||
- name: Report failure to nSentry
|
||||
if: failure()
|
||||
run: |
|
||||
# nself sentry ci enable <repo> must be run on the CamClaw server first.
|
||||
# Once registered, the runner's nself-sentry-sync hook delivers this report
|
||||
# to ~/Sites/acamarata/.claude/inbox via root@sentry-errors.ariccamarata.com.
|
||||
echo "CI_FAILURE repo=${{ github.repository }} job=${{ github.job }} run=${{ github.run_id }}" >&2
|
||||
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Coverage
|
||||
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
|
||||
|
||||
- name: Report failure to nSentry
|
||||
if: failure()
|
||||
run: |
|
||||
# nself sentry ci enable <repo> must be run on the CamClaw server first.
|
||||
# Once registered, the runner's nself-sentry-sync hook delivers this report
|
||||
# to ~/Sites/acamarata/.claude/inbox via root@sentry-errors.ariccamarata.com.
|
||||
echo "CI_FAILURE repo=${{ github.repository }} job=${{ github.job }} run=${{ github.run_id }}" >&2
|
||||
16
.github/wiki/_Sidebar.md
vendored
16
.github/wiki/_Sidebar.md
vendored
|
|
@ -2,6 +2,17 @@
|
|||
|
||||
**[Home](Home)**
|
||||
|
||||
**Guides**
|
||||
- [Quick Start](guides/quickstart)
|
||||
- [Advanced Usage](guides/advanced)
|
||||
|
||||
**API**
|
||||
- [getSpa](api/getSpa)
|
||||
- [calcSpa](api/calcSpa)
|
||||
- [formatTime](api/formatTime)
|
||||
- [SpaOptions](api/SpaOptions)
|
||||
- [SpaFunctionCode](api/SpaFunctionCode)
|
||||
|
||||
**Reference**
|
||||
- [API Reference](API-Reference)
|
||||
- [Architecture](Architecture)
|
||||
|
|
@ -10,6 +21,11 @@
|
|||
**Deep Dives**
|
||||
- [Implementation Comparison](Implementation-Comparison)
|
||||
- [Twilight Calculations](Twilight-Calculations)
|
||||
- [Benchmarks](benchmarks/index)
|
||||
|
||||
**Examples**
|
||||
- [Solar Position Logger](examples/solar-position)
|
||||
- [Twilight Times](examples/twilight-times)
|
||||
|
||||
**Contributing**
|
||||
- [Contributing](Contributing)
|
||||
|
|
|
|||
32
.github/wiki/api/README.md
vendored
Normal file
32
.github/wiki/api/README.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
**nrel-spa v2.0.2**
|
||||
|
||||
***
|
||||
|
||||
# nrel-spa v2.0.2
|
||||
|
||||
## Interfaces
|
||||
|
||||
- [SpaAnglesResult](interfaces/SpaAnglesResult.md)
|
||||
- [SpaFormattedAnglesResult](interfaces/SpaFormattedAnglesResult.md)
|
||||
- [SpaFormattedResult](interfaces/SpaFormattedResult.md)
|
||||
- [SpaFormattedResultWithAngles](interfaces/SpaFormattedResultWithAngles.md)
|
||||
- [SpaOptions](interfaces/SpaOptions.md)
|
||||
- [SpaResult](interfaces/SpaResult.md)
|
||||
- [SpaResultWithAngles](interfaces/SpaResultWithAngles.md)
|
||||
|
||||
## Type Aliases
|
||||
|
||||
- [SpaFunctionCode](type-aliases/SpaFunctionCode.md)
|
||||
|
||||
## Variables
|
||||
|
||||
- [SPA\_ALL](variables/SPA_ALL.md)
|
||||
- [SPA\_ZA](variables/SPA_ZA.md)
|
||||
- [SPA\_ZA\_INC](variables/SPA_ZA_INC.md)
|
||||
- [SPA\_ZA\_RTS](variables/SPA_ZA_RTS.md)
|
||||
|
||||
## Functions
|
||||
|
||||
- [calcSpa](functions/calcSpa.md)
|
||||
- [formatTime](functions/formatTime.md)
|
||||
- [getSpa](functions/getSpa.md)
|
||||
113
.github/wiki/api/functions/calcSpa.md
vendored
Normal file
113
.github/wiki/api/functions/calcSpa.md
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / calcSpa
|
||||
|
||||
# Function: calcSpa()
|
||||
|
||||
## Call Signature
|
||||
|
||||
> **calcSpa**(`date`, `latitude`, `longitude`, `timezone?`, `options?`): [`SpaFormattedResult`](../interfaces/SpaFormattedResult.md)
|
||||
|
||||
Defined in: [index.ts:307](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/index.ts#L307)
|
||||
|
||||
Same as getSpa(), but formats sunrise, solarNoon, and sunset as HH:MM:SS strings.
|
||||
Returns "N/A" for time fields during polar day or polar night.
|
||||
|
||||
### Parameters
|
||||
|
||||
#### date
|
||||
|
||||
`Date`
|
||||
|
||||
JavaScript Date object (uses UTC components)
|
||||
|
||||
#### latitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer latitude in degrees (-90 to 90, negative = south)
|
||||
|
||||
#### longitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer longitude in degrees (-180 to 180, negative = west)
|
||||
|
||||
#### timezone?
|
||||
|
||||
`number` \| `null`
|
||||
|
||||
Hours from UTC (e.g., -4 for EDT). Default: 0
|
||||
|
||||
#### options?
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md) \| `null`
|
||||
|
||||
Optional atmospheric and calculation parameters
|
||||
|
||||
### Returns
|
||||
|
||||
[`SpaFormattedResult`](../interfaces/SpaFormattedResult.md)
|
||||
|
||||
Formatted solar position result with HH:MM:SS time strings
|
||||
|
||||
### Throws
|
||||
|
||||
If date, latitude, longitude, timezone, or options numeric fields are not finite numbers
|
||||
|
||||
### Throws
|
||||
|
||||
If latitude, longitude, timezone, function code, or angle values are out of range
|
||||
|
||||
### See
|
||||
|
||||
[Wiki: calcSpa](https://github.com/acamarata/nrel-spa/wiki/api/calcSpa)
|
||||
|
||||
## Call Signature
|
||||
|
||||
> **calcSpa**(`date`, `latitude`, `longitude`, `timezone`, `options`, `angles`): [`SpaFormattedResultWithAngles`](../interfaces/SpaFormattedResultWithAngles.md)
|
||||
|
||||
Defined in: [index.ts:321](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/index.ts#L321)
|
||||
|
||||
Same as getSpa() with custom angles, but formats all time values as HH:MM:SS strings.
|
||||
Returns "N/A" for time fields during polar day or polar night.
|
||||
|
||||
### Parameters
|
||||
|
||||
#### date
|
||||
|
||||
`Date`
|
||||
|
||||
#### latitude
|
||||
|
||||
`number`
|
||||
|
||||
#### longitude
|
||||
|
||||
`number`
|
||||
|
||||
#### timezone
|
||||
|
||||
`number` \| `null` \| `undefined`
|
||||
|
||||
#### options
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md) \| `null` \| `undefined`
|
||||
|
||||
#### angles
|
||||
|
||||
\[`number`, `...number[]`\]
|
||||
|
||||
### Returns
|
||||
|
||||
[`SpaFormattedResultWithAngles`](../interfaces/SpaFormattedResultWithAngles.md)
|
||||
|
||||
### Throws
|
||||
|
||||
If date, latitude, longitude, timezone, or options numeric fields are not finite numbers
|
||||
|
||||
### Throws
|
||||
|
||||
If latitude, longitude, timezone, function code, or angle values are out of range
|
||||
32
.github/wiki/api/functions/formatTime.md
vendored
Normal file
32
.github/wiki/api/functions/formatTime.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / formatTime
|
||||
|
||||
# Function: formatTime()
|
||||
|
||||
> **formatTime**(`hours`): `string`
|
||||
|
||||
Defined in: [index.ts:87](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/index.ts#L87)
|
||||
|
||||
Format fractional hours to HH:MM:SS string.
|
||||
Returns "N/A" for non-finite or negative values (polar night/day scenarios).
|
||||
|
||||
## Parameters
|
||||
|
||||
### hours
|
||||
|
||||
`number`
|
||||
|
||||
Fractional hours (e.g., 12.5 for 12:30:00)
|
||||
|
||||
## Returns
|
||||
|
||||
`string`
|
||||
|
||||
Formatted time string in HH:MM:SS format, or "N/A"
|
||||
|
||||
## See
|
||||
|
||||
[Wiki: formatTime](https://github.com/acamarata/nrel-spa/wiki/api/formatTime)
|
||||
117
.github/wiki/api/functions/getSpa.md
vendored
Normal file
117
.github/wiki/api/functions/getSpa.md
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / getSpa
|
||||
|
||||
# Function: getSpa()
|
||||
|
||||
## Call Signature
|
||||
|
||||
> **getSpa**(`date`, `latitude`, `longitude`, `timezone?`, `options?`): [`SpaResult`](../interfaces/SpaResult.md)
|
||||
|
||||
Defined in: [index.ts:145](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/index.ts#L145)
|
||||
|
||||
Compute solar position for the given parameters.
|
||||
|
||||
### Parameters
|
||||
|
||||
#### date
|
||||
|
||||
`Date`
|
||||
|
||||
JavaScript Date object (uses UTC components)
|
||||
|
||||
#### latitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer latitude in degrees (-90 to 90, negative = south)
|
||||
|
||||
#### longitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer longitude in degrees (-180 to 180, negative = west)
|
||||
|
||||
#### timezone?
|
||||
|
||||
`number` \| `null`
|
||||
|
||||
Hours from UTC (e.g., -4 for EDT). Default: 0
|
||||
|
||||
#### options?
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md) \| `null`
|
||||
|
||||
Optional atmospheric and calculation parameters
|
||||
|
||||
### Returns
|
||||
|
||||
[`SpaResult`](../interfaces/SpaResult.md)
|
||||
|
||||
Solar position result with raw numerical values
|
||||
|
||||
### Throws
|
||||
|
||||
If date, latitude, longitude, timezone, or options numeric fields are not finite numbers
|
||||
|
||||
### Throws
|
||||
|
||||
If latitude, longitude, timezone, function code, or angle values are out of range
|
||||
|
||||
### See
|
||||
|
||||
[Wiki: getSpa](https://github.com/acamarata/nrel-spa/wiki/api/getSpa)
|
||||
|
||||
## Call Signature
|
||||
|
||||
> **getSpa**(`date`, `latitude`, `longitude`, `timezone`, `options`, `angles`): [`SpaResultWithAngles`](../interfaces/SpaResultWithAngles.md)
|
||||
|
||||
Defined in: [index.ts:163](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/index.ts#L163)
|
||||
|
||||
Compute solar position and resolve custom zenith angles (e.g., twilight).
|
||||
|
||||
### Parameters
|
||||
|
||||
#### date
|
||||
|
||||
`Date`
|
||||
|
||||
JavaScript Date object (uses UTC components)
|
||||
|
||||
#### latitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer latitude in degrees (-90 to 90, negative = south)
|
||||
|
||||
#### longitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer longitude in degrees (-180 to 180, negative = west)
|
||||
|
||||
#### timezone
|
||||
|
||||
`number` \| `null` \| `undefined`
|
||||
|
||||
Hours from UTC (e.g., -4 for EDT). Default: 0
|
||||
|
||||
#### options
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md) \| `null` \| `undefined`
|
||||
|
||||
Atmospheric and calculation parameters (pass null for defaults)
|
||||
|
||||
#### angles
|
||||
|
||||
\[`number`, `...number[]`\]
|
||||
|
||||
Custom zenith angles in degrees. Common: 96 civil, 102 nautical, 108 astronomical
|
||||
|
||||
### Returns
|
||||
|
||||
[`SpaResultWithAngles`](../interfaces/SpaResultWithAngles.md)
|
||||
|
||||
Solar position result including an angles array
|
||||
29
.github/wiki/api/interfaces/SpaAnglesResult.md
vendored
Normal file
29
.github/wiki/api/interfaces/SpaAnglesResult.md
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaAnglesResult
|
||||
|
||||
# Interface: SpaAnglesResult
|
||||
|
||||
Defined in: [types.ts:78](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L78)
|
||||
|
||||
## Properties
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `number`
|
||||
|
||||
Defined in: [types.ts:80](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L80)
|
||||
|
||||
Sunrise time for this custom zenith angle.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `number`
|
||||
|
||||
Defined in: [types.ts:82](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L82)
|
||||
|
||||
Sunset time for this custom zenith angle.
|
||||
29
.github/wiki/api/interfaces/SpaFormattedAnglesResult.md
vendored
Normal file
29
.github/wiki/api/interfaces/SpaFormattedAnglesResult.md
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaFormattedAnglesResult
|
||||
|
||||
# Interface: SpaFormattedAnglesResult
|
||||
|
||||
Defined in: [types.ts:90](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L90)
|
||||
|
||||
## Properties
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `string`
|
||||
|
||||
Defined in: [types.ts:92](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L92)
|
||||
|
||||
Sunrise time for this custom zenith angle, formatted as HH:MM:SS.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `string`
|
||||
|
||||
Defined in: [types.ts:94](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L94)
|
||||
|
||||
Sunset time for this custom zenith angle, formatted as HH:MM:SS.
|
||||
63
.github/wiki/api/interfaces/SpaFormattedResult.md
vendored
Normal file
63
.github/wiki/api/interfaces/SpaFormattedResult.md
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaFormattedResult
|
||||
|
||||
# Interface: SpaFormattedResult
|
||||
|
||||
Defined in: [types.ts:65](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L65)
|
||||
|
||||
## Extended by
|
||||
|
||||
- [`SpaFormattedResultWithAngles`](SpaFormattedResultWithAngles.md)
|
||||
|
||||
## Properties
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:69](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L69)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
***
|
||||
|
||||
### solarNoon
|
||||
|
||||
> **solarNoon**: `string`
|
||||
|
||||
Defined in: [types.ts:73](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L73)
|
||||
|
||||
Local sun transit time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `string`
|
||||
|
||||
Defined in: [types.ts:71](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L71)
|
||||
|
||||
Local sunrise time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `string`
|
||||
|
||||
Defined in: [types.ts:75](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L75)
|
||||
|
||||
Local sunset time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:67](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L67)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
93
.github/wiki/api/interfaces/SpaFormattedResultWithAngles.md
vendored
Normal file
93
.github/wiki/api/interfaces/SpaFormattedResultWithAngles.md
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaFormattedResultWithAngles
|
||||
|
||||
# Interface: SpaFormattedResultWithAngles
|
||||
|
||||
Defined in: [types.ts:97](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L97)
|
||||
|
||||
## Extends
|
||||
|
||||
- [`SpaFormattedResult`](SpaFormattedResult.md)
|
||||
|
||||
## Properties
|
||||
|
||||
### angles
|
||||
|
||||
> **angles**: [`SpaFormattedAnglesResult`](SpaFormattedAnglesResult.md)[]
|
||||
|
||||
Defined in: [types.ts:99](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L99)
|
||||
|
||||
Custom angle results with formatted times.
|
||||
|
||||
***
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:69](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L69)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaFormattedResult`](SpaFormattedResult.md).[`azimuth`](SpaFormattedResult.md#azimuth)
|
||||
|
||||
***
|
||||
|
||||
### solarNoon
|
||||
|
||||
> **solarNoon**: `string`
|
||||
|
||||
Defined in: [types.ts:73](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L73)
|
||||
|
||||
Local sun transit time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaFormattedResult`](SpaFormattedResult.md).[`solarNoon`](SpaFormattedResult.md#solarnoon)
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `string`
|
||||
|
||||
Defined in: [types.ts:71](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L71)
|
||||
|
||||
Local sunrise time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaFormattedResult`](SpaFormattedResult.md).[`sunrise`](SpaFormattedResult.md#sunrise)
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `string`
|
||||
|
||||
Defined in: [types.ts:75](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L75)
|
||||
|
||||
Local sunset time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaFormattedResult`](SpaFormattedResult.md).[`sunset`](SpaFormattedResult.md#sunset)
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:67](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L67)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaFormattedResult`](SpaFormattedResult.md).[`zenith`](SpaFormattedResult.md#zenith)
|
||||
99
.github/wiki/api/interfaces/SpaOptions.md
vendored
Normal file
99
.github/wiki/api/interfaces/SpaOptions.md
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaOptions
|
||||
|
||||
# Interface: SpaOptions
|
||||
|
||||
Defined in: [types.ts:31](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L31)
|
||||
|
||||
## Properties
|
||||
|
||||
### atmos\_refract?
|
||||
|
||||
> `optional` **atmos\_refract?**: `number`
|
||||
|
||||
Defined in: [types.ts:47](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L47)
|
||||
|
||||
Atmospheric refraction at sunrise/sunset in degrees. Default: 0.5667.
|
||||
|
||||
***
|
||||
|
||||
### azm\_rotation?
|
||||
|
||||
> `optional` **azm\_rotation?**: `number`
|
||||
|
||||
Defined in: [types.ts:45](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L45)
|
||||
|
||||
Surface azimuth rotation in degrees from south. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### delta\_t?
|
||||
|
||||
> `optional` **delta\_t?**: `number`
|
||||
|
||||
Defined in: [types.ts:41](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L41)
|
||||
|
||||
TT-UTC difference in seconds. Default: 67.
|
||||
|
||||
***
|
||||
|
||||
### delta\_ut1?
|
||||
|
||||
> `optional` **delta\_ut1?**: `number`
|
||||
|
||||
Defined in: [types.ts:39](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L39)
|
||||
|
||||
UT1-UTC correction in seconds. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### elevation?
|
||||
|
||||
> `optional` **elevation?**: `number`
|
||||
|
||||
Defined in: [types.ts:33](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L33)
|
||||
|
||||
Observer elevation in meters above sea level. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### function?
|
||||
|
||||
> `optional` **function?**: [`SpaFunctionCode`](../type-aliases/SpaFunctionCode.md)
|
||||
|
||||
Defined in: [types.ts:49](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L49)
|
||||
|
||||
SPA function code. Default: SPA_ZA_RTS (2).
|
||||
|
||||
***
|
||||
|
||||
### pressure?
|
||||
|
||||
> `optional` **pressure?**: `number`
|
||||
|
||||
Defined in: [types.ts:35](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L35)
|
||||
|
||||
Atmospheric pressure in millibars. Default: 1013.
|
||||
|
||||
***
|
||||
|
||||
### slope?
|
||||
|
||||
> `optional` **slope?**: `number`
|
||||
|
||||
Defined in: [types.ts:43](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L43)
|
||||
|
||||
Surface slope in degrees from horizontal. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### temperature?
|
||||
|
||||
> `optional` **temperature?**: `number`
|
||||
|
||||
Defined in: [types.ts:37](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L37)
|
||||
|
||||
Temperature in degrees Celsius. Default: 15.
|
||||
63
.github/wiki/api/interfaces/SpaResult.md
vendored
Normal file
63
.github/wiki/api/interfaces/SpaResult.md
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaResult
|
||||
|
||||
# Interface: SpaResult
|
||||
|
||||
Defined in: [types.ts:52](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L52)
|
||||
|
||||
## Extended by
|
||||
|
||||
- [`SpaResultWithAngles`](SpaResultWithAngles.md)
|
||||
|
||||
## Properties
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:56](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L56)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
***
|
||||
|
||||
### solarNoon
|
||||
|
||||
> **solarNoon**: `number`
|
||||
|
||||
Defined in: [types.ts:60](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L60)
|
||||
|
||||
Local sun transit time (solar noon) as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `number`
|
||||
|
||||
Defined in: [types.ts:58](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L58)
|
||||
|
||||
Local sunrise time as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `number`
|
||||
|
||||
Defined in: [types.ts:62](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L62)
|
||||
|
||||
Local sunset time as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:54](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L54)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
93
.github/wiki/api/interfaces/SpaResultWithAngles.md
vendored
Normal file
93
.github/wiki/api/interfaces/SpaResultWithAngles.md
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaResultWithAngles
|
||||
|
||||
# Interface: SpaResultWithAngles
|
||||
|
||||
Defined in: [types.ts:85](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L85)
|
||||
|
||||
## Extends
|
||||
|
||||
- [`SpaResult`](SpaResult.md)
|
||||
|
||||
## Properties
|
||||
|
||||
### angles
|
||||
|
||||
> **angles**: [`SpaAnglesResult`](SpaAnglesResult.md)[]
|
||||
|
||||
Defined in: [types.ts:87](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L87)
|
||||
|
||||
Custom angle results, one per angle in the input array.
|
||||
|
||||
***
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:56](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L56)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`azimuth`](SpaResult.md#azimuth)
|
||||
|
||||
***
|
||||
|
||||
### solarNoon
|
||||
|
||||
> **solarNoon**: `number`
|
||||
|
||||
Defined in: [types.ts:60](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L60)
|
||||
|
||||
Local sun transit time (solar noon) as fractional hours.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`solarNoon`](SpaResult.md#solarnoon)
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `number`
|
||||
|
||||
Defined in: [types.ts:58](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L58)
|
||||
|
||||
Local sunrise time as fractional hours.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`sunrise`](SpaResult.md#sunrise)
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `number`
|
||||
|
||||
Defined in: [types.ts:62](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L62)
|
||||
|
||||
Local sunset time as fractional hours.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`sunset`](SpaResult.md#sunset)
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:54](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L54)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`zenith`](SpaResult.md#zenith)
|
||||
11
.github/wiki/api/type-aliases/SpaFunctionCode.md
vendored
Normal file
11
.github/wiki/api/type-aliases/SpaFunctionCode.md
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SpaFunctionCode
|
||||
|
||||
# Type Alias: SpaFunctionCode
|
||||
|
||||
> **SpaFunctionCode** = *typeof* [`SPA_ZA`](../variables/SPA_ZA.md) \| *typeof* [`SPA_ZA_INC`](../variables/SPA_ZA_INC.md) \| *typeof* [`SPA_ZA_RTS`](../variables/SPA_ZA_RTS.md) \| *typeof* [`SPA_ALL`](../variables/SPA_ALL.md)
|
||||
|
||||
Defined in: [types.ts:25](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L25)
|
||||
14
.github/wiki/api/variables/SPA_ALL.md
vendored
Normal file
14
.github/wiki/api/variables/SPA_ALL.md
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SPA\_ALL
|
||||
|
||||
# Variable: SPA\_ALL
|
||||
|
||||
> `const` **SPA\_ALL**: `3`
|
||||
|
||||
Defined in: [types.ts:23](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L23)
|
||||
|
||||
Compute all outputs: zenith, azimuth, incidence angle, sunrise, sunset,
|
||||
and sun transit. Combines SPA_ZA_INC and SPA_ZA_RTS.
|
||||
14
.github/wiki/api/variables/SPA_ZA.md
vendored
Normal file
14
.github/wiki/api/variables/SPA_ZA.md
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SPA\_ZA
|
||||
|
||||
# Variable: SPA\_ZA
|
||||
|
||||
> `const` **SPA\_ZA**: `0`
|
||||
|
||||
Defined in: [types.ts:5](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L5)
|
||||
|
||||
Compute topocentric zenith and azimuth angles only.
|
||||
Does not compute sunrise, sunset, or solar noon.
|
||||
14
.github/wiki/api/variables/SPA_ZA_INC.md
vendored
Normal file
14
.github/wiki/api/variables/SPA_ZA_INC.md
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SPA\_ZA\_INC
|
||||
|
||||
# Variable: SPA\_ZA\_INC
|
||||
|
||||
> `const` **SPA\_ZA\_INC**: `1`
|
||||
|
||||
Defined in: [types.ts:11](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L11)
|
||||
|
||||
Compute zenith, azimuth, and incidence angle for a tilted surface.
|
||||
Requires slope and azm_rotation in SpaOptions.
|
||||
14
.github/wiki/api/variables/SPA_ZA_RTS.md
vendored
Normal file
14
.github/wiki/api/variables/SPA_ZA_RTS.md
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[**nrel-spa v2.0.2**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[nrel-spa](../README.md) / SPA\_ZA\_RTS
|
||||
|
||||
# Variable: SPA\_ZA\_RTS
|
||||
|
||||
> `const` **SPA\_ZA\_RTS**: `2`
|
||||
|
||||
Defined in: [types.ts:17](https://github.com/acamarata/nrel-spa/blob/b52802f94b8c28a03228118f51c17ce21d4c14b3/src/types.ts#L17)
|
||||
|
||||
Compute sunrise, sunset, and sun transit (solar noon) in addition to
|
||||
zenith and azimuth. This is the default function code.
|
||||
70
.github/wiki/benchmarks/index.md
vendored
Normal file
70
.github/wiki/benchmarks/index.md
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Benchmarks
|
||||
|
||||
Performance and bundle size measurements for nrel-spa, with comparisons to solar-spa.
|
||||
|
||||
## Bundle Size
|
||||
|
||||
Measured from `dist/` after `pnpm build`.
|
||||
|
||||
| File | Raw | Gzipped |
|
||||
|------------------|-------|---------|
|
||||
| `index.mjs` (ESM) | 5.8 KB | 2.0 KB |
|
||||
| `index.cjs` (CJS) | 6.5 KB | 2.2 KB |
|
||||
|
||||
The package also includes `lib/spa.cjs` (the core algorithm), which adds ~38 KB raw. The full `nrel-spa` npm pack total is under 100 KB uncompressed.
|
||||
|
||||
solar-spa bundles a compiled WASM binary. Total npm pack size is approximately 60 KB gzipped, compared to ~40 KB for nrel-spa.
|
||||
|
||||
## Throughput
|
||||
|
||||
**Environment:** Node.js v24.6.0, macOS. 200,000 iterations per measurement with a 2,000-iteration warm-up. Test case: New York summer solstice, `SPA_ZA_RTS` (full rise/set calculation).
|
||||
|
||||
| Implementation | ns/call | calls/s |
|
||||
|----------------|---------|----------|
|
||||
| nrel-spa | 84,497 | 11,835 |
|
||||
| solar-spa | 45,139 | 22,154 |
|
||||
|
||||
**solar-spa WASM is 1.5-1.9x faster** for sustained throughput. The WASM binary is compiled from the same C source with `-O2`.
|
||||
|
||||
Switching to `SPA_ZA` (zenith and azimuth only, no rise/set) removes the three-day interpolation step:
|
||||
|
||||
| Implementation | Mode | ns/call | calls/s |
|
||||
|----------------|---------------|---------|---------|
|
||||
| nrel-spa | SPA_ZA_RTS | 84,497 | 11,835 |
|
||||
| nrel-spa | SPA_ZA | 9,284 | 107,711 |
|
||||
| solar-spa | SPA_ZA_RTS | 45,139 | 22,154 |
|
||||
| solar-spa | SPA_ZA | 6,112 | 163,616 |
|
||||
|
||||
`SPA_ZA` is roughly 9x faster in nrel-spa. Use it when rise/set times are not needed.
|
||||
|
||||
## When Each Package Fits
|
||||
|
||||
| Scenario | Recommendation |
|
||||
|---|---|
|
||||
| Serverless, edge workers, middleware | nrel-spa (synchronous, zero init, smaller) |
|
||||
| Single request in a Node server | nrel-spa (no async overhead) |
|
||||
| Batch pre-computation (100K+ calls) | solar-spa (1.5-1.9x faster after init) |
|
||||
| Custom twilight angles | nrel-spa (solar-spa does not support custom zenith angles) |
|
||||
|
||||
## Methodology
|
||||
|
||||
Throughput numbers above come from the manual benchmark in [`bin/`](https://github.com/acamarata/nrel-spa/tree/main/bin) and the accuracy analysis in [Implementation Comparison](../Implementation-Comparison). To reproduce:
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
node bin/bench.mjs # or see bin/README.md for full setup
|
||||
```
|
||||
|
||||
Bundle sizes are reproducible with:
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
du -sh dist/index.mjs dist/index.cjs
|
||||
gzip -c dist/index.mjs | wc -c # gzipped ESM
|
||||
gzip -c dist/index.cjs | wc -c # gzipped CJS
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implementation Comparison](../Implementation-Comparison) - accuracy table and full API convention analysis
|
||||
- [Architecture](../Architecture) - module structure and algorithm overview
|
||||
40
.github/wiki/examples/solar-position.md
vendored
Normal file
40
.github/wiki/examples/solar-position.md
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Example: Solar Position Logger
|
||||
|
||||
Print solar position every 30 minutes throughout a day.
|
||||
|
||||
```js
|
||||
import { calcSpa } from 'nrel-spa';
|
||||
|
||||
const LAT = 40.7128; // New York
|
||||
const LON = -74.0060;
|
||||
const TZ = -4; // EDT
|
||||
|
||||
const date = new Date('2025-06-21T00:00:00-04:00');
|
||||
|
||||
console.log('Time (local) Zenith Azimuth');
|
||||
console.log('──────────── ──────── ────────');
|
||||
|
||||
for (let hour = 4; hour <= 21; hour++) {
|
||||
for (const min of [0, 30]) {
|
||||
const d = new Date(date);
|
||||
d.setHours(hour, min, 0, 0);
|
||||
const r = calcSpa(d, LAT, LON, TZ);
|
||||
const timeStr = `${String(hour).padStart(2, '0')}:${String(min).padStart(2, '0')}`;
|
||||
const zenith = Number(r.zenith).toFixed(1).padStart(7);
|
||||
const azimuth = Number(r.azimuth).toFixed(1).padStart(7);
|
||||
console.log(`${timeStr} ${zenith}° ${azimuth}°`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample output (abbreviated):
|
||||
|
||||
```
|
||||
Time (local) Zenith Azimuth
|
||||
──────────── ──────── ────────
|
||||
05:30 89.3° 60.2°
|
||||
06:00 83.1° 68.4°
|
||||
12:00 24.6° 185.1°
|
||||
13:00 26.1° 212.3°
|
||||
20:00 83.8° 293.7°
|
||||
```
|
||||
33
.github/wiki/examples/twilight-times.md
vendored
Normal file
33
.github/wiki/examples/twilight-times.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Example: Twilight Times
|
||||
|
||||
Compute civil, nautical, and astronomical twilight for a location.
|
||||
|
||||
```js
|
||||
import { getSpa, formatTime } from 'nrel-spa';
|
||||
|
||||
const LAT = 34.0522; // Los Angeles
|
||||
const LON = -118.2437;
|
||||
const TZ = -7; // PDT
|
||||
|
||||
const date = new Date('2025-09-15T12:00:00-07:00');
|
||||
|
||||
const r = getSpa(date, LAT, LON, TZ, null, [90.833, 96, 102, 108]);
|
||||
|
||||
const labels = ['Sunrise/Sunset', 'Civil', 'Nautical', 'Astronomical'];
|
||||
|
||||
for (let i = 0; i < r.angles.length; i++) {
|
||||
const ca = r.angles[i];
|
||||
const rise = formatTime(ca.sunrise);
|
||||
const set = formatTime(ca.sunset);
|
||||
console.log(`${labels[i].padEnd(16)} dawn: ${rise} dusk: ${set}`);
|
||||
}
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
Sunrise/Sunset dawn: 06:30:42 dusk: 19:12:18
|
||||
Civil dawn: 06:04:11 dusk: 19:38:49
|
||||
Nautical dawn: 05:33:07 dusk: 20:09:53
|
||||
Astronomical dawn: 05:01:19 dusk: 20:41:41
|
||||
```
|
||||
78
.github/wiki/guides/advanced.md
vendored
Normal file
78
.github/wiki/guides/advanced.md
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Advanced Usage
|
||||
|
||||
## Twilight calculations
|
||||
|
||||
Pass an array of custom zenith angles to compute additional rise/set events beyond the default solar disk (90.833°).
|
||||
|
||||
```js
|
||||
import { getSpa } from 'nrel-spa';
|
||||
|
||||
const result = getSpa(date, lat, lon, tz, {}, [96, 102, 108]);
|
||||
|
||||
for (const ca of result.customAngles) {
|
||||
const label = ca.angle === 96 ? 'Civil' : ca.angle === 102 ? 'Nautical' : 'Astronomical';
|
||||
console.log(`${label} dawn: ${ca.sunrise.toFixed(4)} hours`);
|
||||
console.log(`${label} dusk: ${ca.sunset.toFixed(4)} hours`);
|
||||
}
|
||||
```
|
||||
|
||||
Zenith reference:
|
||||
- 90.833° — standard solar disk (default)
|
||||
- 96° — civil twilight
|
||||
- 102° — nautical twilight
|
||||
- 108° — astronomical twilight
|
||||
|
||||
## Batch processing
|
||||
|
||||
`getSpa` is synchronous, making batch work straightforward without async coordination.
|
||||
|
||||
```js
|
||||
import { getSpa } from 'nrel-spa';
|
||||
|
||||
const lat = 51.5074; // London
|
||||
const lon = -0.1278;
|
||||
const tz = 0;
|
||||
|
||||
let totalDaylight = 0;
|
||||
for (let doy = 0; doy < 365; doy++) {
|
||||
const d = new Date(Date.UTC(2025, 0, 1 + doy, 12, 0, 0));
|
||||
const r = getSpa(d, lat, lon, tz);
|
||||
if (isFinite(r.sunrise)) {
|
||||
totalDaylight += r.sunset - r.sunrise;
|
||||
}
|
||||
}
|
||||
console.log(`Annual daylight: ${totalDaylight.toFixed(0)} hours`);
|
||||
```
|
||||
|
||||
## Polar scenarios
|
||||
|
||||
At high latitudes, `sunrise` and `sunset` are `NaN` when the sun does not cross the horizon. Check with `isFinite()`.
|
||||
|
||||
```js
|
||||
const r = getSpa(new Date('2025-12-21T12:00:00Z'), 89, 0, 0);
|
||||
console.log(isFinite(r.sunrise)); // false — polar night
|
||||
```
|
||||
|
||||
`calcSpa` returns `"N/A"` strings in these cases.
|
||||
|
||||
## vs solar-spa
|
||||
|
||||
Both packages implement the same NREL SPA algorithm. Key differences:
|
||||
|
||||
| | nrel-spa | solar-spa |
|
||||
|---|---|---|
|
||||
| Runtime | Pure JS | WebAssembly |
|
||||
| API | Synchronous | Async (Promise) |
|
||||
| Custom zenith | Yes | No |
|
||||
| Bundle size | ~38 KB | ~60 KB |
|
||||
| Init latency | None | First call |
|
||||
|
||||
Use `nrel-spa` when you need synchronous calls or custom twilight angles. Use `solar-spa` when throughput for very large batches matters.
|
||||
|
||||
## Delta-T
|
||||
|
||||
The default `delta_t` is 67 seconds, accurate for dates near 2025. For historical dates or high-precision work, provide a value from the IERS or USNO tables.
|
||||
|
||||
```js
|
||||
getSpa(new Date('1900-01-01T12:00:00Z'), lat, lon, tz, { delta_t: -2.72 });
|
||||
```
|
||||
77
.github/wiki/guides/quickstart.md
vendored
Normal file
77
.github/wiki/guides/quickstart.md
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Quick Start
|
||||
|
||||
Five minutes from install to solar position.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install nrel-spa
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
```js
|
||||
import { getSpa } from 'nrel-spa';
|
||||
|
||||
const result = getSpa(
|
||||
new Date('2025-06-21T12:00:00Z'),
|
||||
40.7128, // latitude
|
||||
-74.0060, // longitude
|
||||
-4, // UTC offset in hours (EDT)
|
||||
);
|
||||
|
||||
console.log(result.zenith); // solar zenith angle in degrees
|
||||
console.log(result.azimuth); // solar azimuth in degrees
|
||||
console.log(result.sunrise); // fractional hours, e.g. 5.42
|
||||
console.log(result.sunset); // fractional hours, e.g. 20.58
|
||||
console.log(result.solarNoon); // fractional hours, e.g. 13.00
|
||||
```
|
||||
|
||||
`getSpa` is synchronous. There is no initialization step, no WASM loading, no async overhead.
|
||||
|
||||
## Formatted output
|
||||
|
||||
```js
|
||||
import { calcSpa } from 'nrel-spa';
|
||||
|
||||
const result = calcSpa(
|
||||
new Date('2025-06-21T12:00:00Z'),
|
||||
40.7128,
|
||||
-74.0060,
|
||||
-4,
|
||||
);
|
||||
|
||||
console.log(result.sunrise); // "05:25:12"
|
||||
console.log(result.sunset); // "20:34:47"
|
||||
console.log(result.solarNoon); // "12:59:58"
|
||||
```
|
||||
|
||||
## Custom zenith angles (twilight)
|
||||
|
||||
```js
|
||||
import { getSpa } from 'nrel-spa';
|
||||
|
||||
const civil = getSpa(date, lat, lon, tz, {}, [96]); // civil twilight
|
||||
const nautical = getSpa(date, lat, lon, tz, {}, [102]); // nautical twilight
|
||||
const astro = getSpa(date, lat, lon, tz, {}, [108]); // astronomical twilight
|
||||
|
||||
console.log(civil.customAngles[0].sunrise); // civil dawn
|
||||
console.log(nautical.customAngles[0].sunrise); // nautical dawn
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```js
|
||||
const result = getSpa(date, lat, lon, tz, {
|
||||
elevation: 100, // metres above sea level
|
||||
pressure: 1013.25, // millibars
|
||||
temperature: 15, // Celsius
|
||||
delta_t: 67, // ΔT in seconds
|
||||
});
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [API Reference](../API-Reference) — full function signatures and return types
|
||||
- [Architecture](../Architecture) — module structure and algorithm notes
|
||||
- [Advanced Guide](advanced) — twilight, batch calculations, polar scenarios
|
||||
43
.github/workflows/ci.yml
vendored
43
.github/workflows/ci.yml
vendored
|
|
@ -16,14 +16,14 @@ jobs:
|
|||
node-version: [20, 22, 24]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
|
|
@ -42,14 +42,14 @@ jobs:
|
|||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run typecheck
|
||||
|
|
@ -59,12 +59,12 @@ jobs:
|
|||
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
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run lint
|
||||
- run: pnpm run format:check
|
||||
|
|
@ -75,14 +75,14 @@ jobs:
|
|||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
|
|
@ -94,3 +94,30 @@ jobs:
|
|||
grep -qE "(^|[[:space:]])${f}([[:space:]]|$)" pack-output.txt || { echo "MISSING: $f"; exit 1; }
|
||||
done
|
||||
echo "All expected files present in package"
|
||||
|
||||
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
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Coverage
|
||||
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
|
||||
|
|
|
|||
8
.github/workflows/wiki-sync.yml
vendored
8
.github/workflows/wiki-sync.yml
vendored
|
|
@ -25,7 +25,15 @@ jobs:
|
|||
|
||||
- name: Sync wiki pages
|
||||
run: |
|
||||
# Copy root wiki pages
|
||||
cp .github/wiki/*.md .wiki-remote/
|
||||
# Copy subdirectory pages (api/, benchmarks/, guides/, examples/)
|
||||
for dir in api benchmarks guides examples; do
|
||||
if [ -d ".github/wiki/$dir" ]; then
|
||||
mkdir -p ".wiki-remote/$dir"
|
||||
cp ".github/wiki/$dir"/*.md ".wiki-remote/$dir/" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
cd .wiki-remote
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
*.tgz
|
||||
*.log
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
[](https://www.npmjs.com/package/nrel-spa)
|
||||
[](https://github.com/acamarata/nrel-spa/actions/workflows/ci.yml)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/acamarata/nrel-spa/wiki)
|
||||
|
||||
Pure JavaScript implementation of the NREL Solar Position Algorithm (SPA). Computes solar zenith angle, azimuth, sunrise, sunset, and solar noon for any location and date. Zero dependencies, synchronous. Validated to produce identical results to the original NREL C reference implementation.
|
||||
|
||||
|
|
@ -65,3 +66,7 @@ The core algorithm is a JavaScript port of the NREL SPA by Ibrahim Reda and Afsh
|
|||
## License
|
||||
|
||||
MIT (TypeScript wrapper and build tooling). The core algorithm in `lib/spa.js` is a port of NREL's SPA C source, subject to its own terms. See [LICENSE](./LICENSE).
|
||||
|
||||
## Telemetry
|
||||
|
||||
This package supports optional, anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry). It is **off by default**. See [TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md) for what is collected and how to enable or disable it.
|
||||
|
|
|
|||
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,12 +1,33 @@
|
|||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||
import { typescript } from '@acamarata/eslint-config';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
plugins: { '@typescript-eslint': tsPlugin },
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
...typescript.map((c) => ({
|
||||
...c,
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
...(c.languageOptions ?? {}),
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
...((c.languageOptions ?? {}).parserOptions ?? {}),
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
})),
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs', 'lib/'],
|
||||
},
|
||||
);
|
||||
];
|
||||
|
|
|
|||
18
package.json
18
package.json
|
|
@ -34,8 +34,10 @@
|
|||
"lint": "eslint src/",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"prepublishOnly": "tsup",
|
||||
"coverage": "c8 --reporter=lcov --reporter=text node --test"
|
||||
"prepack": "pnpm run build",
|
||||
"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"
|
||||
},
|
||||
"keywords": [
|
||||
"solar",
|
||||
|
|
@ -67,14 +69,24 @@
|
|||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acamarata/eslint-config": "^0.1.0",
|
||||
"@acamarata/prettier-config": "^0.1.0",
|
||||
"@acamarata/telemetry": "^0.1.0",
|
||||
"@acamarata/tsconfig": "^0.1.0",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/node": "^25.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"c8": "^10.1.3",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.8.1",
|
||||
"tsup": "^8.5.1",
|
||||
"typedoc": "^0.28.19",
|
||||
"typedoc-plugin-markdown": "^4.11.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.1"
|
||||
"packageManager": "pnpm@10.11.1",
|
||||
"prettier": "@acamarata/prettier-config"
|
||||
}
|
||||
|
|
|
|||
645
pnpm-lock.yaml
645
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
66
src/index.ts
66
src/index.ts
|
|
@ -7,11 +7,11 @@ export type {
|
|||
SpaResultWithAngles,
|
||||
SpaFormattedResultWithAngles,
|
||||
SpaFunctionCode,
|
||||
} from './types.js';
|
||||
} from "./types.js";
|
||||
|
||||
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from './types.js';
|
||||
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from "./types.js";
|
||||
|
||||
import { SPA_ZA_RTS } from './types.js';
|
||||
import { SPA_ZA_RTS } from "./types.js";
|
||||
import type {
|
||||
SpaOptions,
|
||||
SpaResult,
|
||||
|
|
@ -19,7 +19,7 @@ import type {
|
|||
SpaResultWithAngles,
|
||||
SpaFormattedResultWithAngles,
|
||||
SpaFormattedAnglesResult,
|
||||
} from './types.js';
|
||||
} from "./types.js";
|
||||
|
||||
/** Degrees-to-radians conversion factor. */
|
||||
const DEG = Math.PI / 180;
|
||||
|
|
@ -28,8 +28,8 @@ const DEG = Math.PI / 180;
|
|||
// In ESM builds, tsup injects a createRequire-based __require shim via the banner
|
||||
// option (see tsup.config.ts). In CJS builds, require() is natively available.
|
||||
declare const __require: NodeRequire;
|
||||
const _load = typeof __require === 'function' ? __require : require;
|
||||
const spa = _load('../lib/spa.cjs') as {
|
||||
const _load = typeof __require === "function" ? __require : require;
|
||||
const spa = _load("../lib/spa.cjs") as {
|
||||
SpaData: new () => SpaDataInstance;
|
||||
SPA_ZA_RTS: number;
|
||||
spa_calculate: (data: SpaDataInstance) => number;
|
||||
|
|
@ -69,9 +69,9 @@ interface SpaDataInstance {
|
|||
* @internal
|
||||
*/
|
||||
function assertFiniteNumber(value: unknown, name: string): asserts value is number {
|
||||
if (typeof value !== 'number' || !isFinite(value)) {
|
||||
if (typeof value !== "number" || !isFinite(value)) {
|
||||
throw new TypeError(
|
||||
`SPA: ${name} must be a finite number, got ${typeof value === 'number' ? value : typeof value}`,
|
||||
`SPA: ${name} must be a finite number, got ${typeof value === "number" ? value : typeof value}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -82,9 +82,10 @@ function assertFiniteNumber(value: unknown, name: string): asserts value is numb
|
|||
*
|
||||
* @param hours - Fractional hours (e.g., 12.5 for 12:30:00)
|
||||
* @returns Formatted time string in HH:MM:SS format, or "N/A"
|
||||
* @see {@link https://github.com/acamarata/nrel-spa/wiki/api/formatTime Wiki: formatTime}
|
||||
*/
|
||||
export function formatTime(hours: number): string {
|
||||
if (!isFinite(hours) || hours < 0) return 'N/A';
|
||||
if (!isFinite(hours) || hours < 0) return "N/A";
|
||||
|
||||
const totalSec = Math.round(hours * 3600);
|
||||
// Wrap at 24h: values near midnight can round to 24:00:00
|
||||
|
|
@ -94,7 +95,7 @@ export function formatTime(hours: number): string {
|
|||
const s = rem - m * 60;
|
||||
|
||||
return (
|
||||
String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0')
|
||||
String(h).padStart(2, "0") + ":" + String(m).padStart(2, "0") + ":" + String(s).padStart(2, "0")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +140,7 @@ function adjustForCustomAngle(
|
|||
* @returns Solar position result with raw numerical values
|
||||
* @throws {TypeError} If date, latitude, longitude, timezone, or options numeric fields are not finite numbers
|
||||
* @throws {RangeError} If latitude, longitude, timezone, function code, or angle values are out of range
|
||||
* @see {@link https://github.com/acamarata/nrel-spa/wiki/api/getSpa Wiki: getSpa}
|
||||
*/
|
||||
export function getSpa(
|
||||
date: Date,
|
||||
|
|
@ -175,10 +177,10 @@ export function getSpa(
|
|||
angles?: number[],
|
||||
): SpaResult | SpaResultWithAngles {
|
||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||
throw new TypeError('SPA: date must be a valid Date object');
|
||||
throw new TypeError("SPA: date must be a valid Date object");
|
||||
}
|
||||
assertFiniteNumber(latitude, 'latitude');
|
||||
assertFiniteNumber(longitude, 'longitude');
|
||||
assertFiniteNumber(latitude, "latitude");
|
||||
assertFiniteNumber(longitude, "longitude");
|
||||
|
||||
if (latitude < -90 || latitude > 90) {
|
||||
throw new RangeError(`SPA: latitude must be between -90 and 90, got ${latitude}`);
|
||||
|
|
@ -188,7 +190,7 @@ export function getSpa(
|
|||
}
|
||||
|
||||
const tz = timezone ?? 0;
|
||||
assertFiniteNumber(tz, 'timezone');
|
||||
assertFiniteNumber(tz, "timezone");
|
||||
if (tz < -18 || tz > 18) {
|
||||
throw new RangeError(`SPA: timezone must be between -18 and 18, got ${tz}`);
|
||||
}
|
||||
|
|
@ -196,13 +198,13 @@ export function getSpa(
|
|||
const opts = options ?? {};
|
||||
|
||||
const optNumericFields = [
|
||||
'elevation',
|
||||
'pressure',
|
||||
'temperature',
|
||||
'delta_t',
|
||||
'slope',
|
||||
'azm_rotation',
|
||||
'atmos_refract',
|
||||
"elevation",
|
||||
"pressure",
|
||||
"temperature",
|
||||
"delta_t",
|
||||
"slope",
|
||||
"azm_rotation",
|
||||
"atmos_refract",
|
||||
] as const;
|
||||
for (const field of optNumericFields) {
|
||||
if (opts[field] !== undefined) {
|
||||
|
|
@ -221,9 +223,9 @@ export function getSpa(
|
|||
if (angles && angles.length > 0) {
|
||||
for (let i = 0; i < angles.length; i++) {
|
||||
const a = angles[i];
|
||||
if (typeof a !== 'number' || !isFinite(a)) {
|
||||
if (typeof a !== "number" || !isFinite(a)) {
|
||||
throw new TypeError(
|
||||
`SPA: angles[${i}] must be a finite number, got ${typeof a === 'number' ? a : typeof a}`,
|
||||
`SPA: angles[${i}] must be a finite number, got ${typeof a === "number" ? a : typeof a}`,
|
||||
);
|
||||
}
|
||||
if (a < 0 || a > 180) {
|
||||
|
|
@ -235,7 +237,7 @@ export function getSpa(
|
|||
// Custom angle calculations depend on suntransit, which requires an RTS function code.
|
||||
if (angles && angles.length > 0 && fnCode !== 2 && fnCode !== 3) {
|
||||
throw new RangeError(
|
||||
'SPA: custom zenith angle calculations require an RTS function code (SPA_ZA_RTS or SPA_ALL)',
|
||||
"SPA: custom zenith angle calculations require an RTS function code (SPA_ZA_RTS or SPA_ALL)",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -292,8 +294,15 @@ export function getSpa(
|
|||
* Same as getSpa(), but formats sunrise, solarNoon, and sunset as HH:MM:SS strings.
|
||||
* Returns "N/A" for time fields during polar day or polar night.
|
||||
*
|
||||
* @param date - JavaScript Date object (uses UTC components)
|
||||
* @param latitude - Observer latitude in degrees (-90 to 90, negative = south)
|
||||
* @param longitude - Observer longitude in degrees (-180 to 180, negative = west)
|
||||
* @param timezone - Hours from UTC (e.g., -4 for EDT). Default: 0
|
||||
* @param options - Optional atmospheric and calculation parameters
|
||||
* @returns Formatted solar position result with HH:MM:SS time strings
|
||||
* @throws {TypeError} If date, latitude, longitude, timezone, or options numeric fields are not finite numbers
|
||||
* @throws {RangeError} If latitude, longitude, timezone, function code, or angle values are out of range
|
||||
* @see {@link https://github.com/acamarata/nrel-spa/wiki/api/calcSpa Wiki: calcSpa}
|
||||
*/
|
||||
export function calcSpa(
|
||||
date: Date,
|
||||
|
|
@ -358,3 +367,12 @@ export function calcSpa(
|
|||
sunset: formatTime(raw.sunset),
|
||||
};
|
||||
}
|
||||
|
||||
// ── 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: "nrel-spa", version: "2.0.2" }))
|
||||
.catch(() => {
|
||||
// telemetry not installed or disabled — that's fine
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,6 @@
|
|||
{
|
||||
"extends": "@acamarata/tsconfig/tsconfig.library.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020"],
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"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