Complete modernization of the package. The core SPA algorithm is unchanged and validated; everything else is rebuilt to match current JavaScript ecosystem standards. Changes: - TypeScript wrapper in src/ with full type definitions - Dual CJS/ESM build via tsup (dist/index.cjs, dist/index.mjs) - Core algorithm moved from dist/spa.js to lib/spa.js (same code) - Input validation with descriptive TypeError/RangeError messages - formatTime() and SPA function code constants as named exports - getSpa() / calcSpa() accept null for optional args (tz, options) - Test suite: 61 ESM assertions and 17 CJS assertions - GitHub Actions CI: Node 20/22/24 matrix, typecheck, pack-check - GitHub Wiki: Home, API Reference, Architecture, Twilight, NREL SPA - NREL attribution in LICENSE and README per their license terms - package.json: exports map, files, engines >=20, sideEffects: false - Author corrected to Aric Camarata; repository.url uses git+https:// - LICENSE year corrected to 2023-2026 - Removed: index.js, test.js, dist/spa.js (superseded by above)
4.8 KiB
NREL SPA Algorithm
Background
The Solar Position Algorithm (SPA) was developed by Ibrahim Reda and Afshin Andreas at the National Renewable Energy Laboratory (NREL) and published in 2004. It is the reference algorithm for solar position calculation in scientific and engineering applications.
Citation:
Reda, I., Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." Solar Energy, 76(5), 577-589. https://doi.org/10.1016/j.solener.2003.12.003
Original C source: https://midcdmz.nrel.gov/spa/
The C source has not changed since its last release in September 2014. This JavaScript port is validated against that version.
Accuracy
The algorithm achieves uncertainty of +/- 0.0003 degrees in solar zenith and azimuth angles. For sunrise, solar noon, and sunset, results match the C reference to within one second across all tested locations and dates.
Validation test cases from bin/test.js:
| Location | Date | Sunrise (C) | Sunrise (JS) | Noon (C) | Noon (JS) | Sunset (C) | Sunset (JS) |
|---|---|---|---|---|---|---|---|
| New York | 2025-06-21 | 05:25:03 | 05:25:03 | 12:57:56 | 12:57:56 | 20:30:35 | 20:30:35 |
| New York | 2025-12-21 | 07:16:41 | 07:16:41 | 11:54:19 | 11:54:19 | 16:31:56 | 16:31:56 |
| London | 2025-06-21 | 04:43:07 | 04:43:07 | 13:02:22 | 13:02:22 | 21:21:37 | 21:21:37 |
| London | 2025-12-21 | 08:03:52 | 08:03:52 | 11:58:42 | 11:58:42 | 15:53:32 | 15:53:32 |
| Tokyo | 2025-06-21 | 04:25:52 | 04:25:52 | 11:43:00 | 11:43:00 | 19:00:22 | 19:00:22 |
| Sydney | 2025-06-21 | 07:00:12 | 07:00:12 | 11:56:56 | 11:56:56 | 16:53:52 | 16:53:52 |
| Reykjavik | 2025-06-21 | 02:55:10 | 02:55:10 | 13:29:38 | 13:29:38 | 00:03:54 | 00:03:54 |
| Cape Town | 2025-12-21 | 05:31:55 | 05:31:55 | 12:44:28 | 12:44:28 | 19:57:01 | 19:57:01 |
| Quito | 2025-03-20 | 06:17:54 | 06:17:54 | 12:21:10 | 12:21:10 | 18:24:25 | 18:24:25 |
| Tromso | 2025-12-21 | N/A | N/A | N/A | N/A | N/A | N/A |
Zero drift in all cases with sun (Tromso is polar night in December).
Algorithm Outline
The SPA computes solar position through a chain of coordinate transformations:
-
Julian Day (JD) from the input date and time, accounting for the timezone offset and DUT1 correction.
-
Earth Heliocentric Coordinates using Variations Seculaires des Orbites Planetaires (VSOP87) truncated to 63 periodic terms for longitude (L), 5 terms for latitude (B), and 40 terms for radius (R).
-
Geocentric Coordinates by converting heliocentric L to geocentric longitude (theta) and negating the latitude (beta = -B).
-
Nutation in longitude (del_psi) and obliquity (del_epsilon) from 63 periodic terms in the IAU 1980 nutation model.
-
Apparent Sun Longitude by adding aberration correction and nutation to theta.
-
Greenwich Sidereal Time and then the Observer Hour Angle (H).
-
Topocentric Correction using the observer's elevation, converting geocentric right ascension and declination to topocentric values (alpha_prime, delta_prime, h_prime).
-
Zenith and Azimuth from topocentric elevation angle corrected for atmospheric refraction.
-
Rise/Transit/Set via a three-day calculation: the algorithm solves for sunrise, solar noon, and sunset by interpolating right ascension and declination across the previous, current, and next day, then iterating to correct the approximate times.
Key Parameters
delta_t (TT-UTC, default 67 seconds): the accumulated difference between Terrestrial Time (an ideal clock) and UTC (subject to leap seconds). The NREL bulletin value for 2025 is approximately 68-70 seconds. The default of 67 is suitable for dates within a few years of 2020.
delta_ut1 (UT1-UTC, default 0): a sub-second correction published by the IERS. For most applications, the default of 0 is acceptable.
atmos_refract (default 0.5667 degrees): atmospheric refraction at the horizon. This shifts the apparent sunrise earlier and sunset later than geometric calculations. The NREL default matches standard atmospheric conditions.
Comparison with solar-spa
solar-spa compiles the same NREL C source to WebAssembly via Emscripten. The two packages share the same algorithm and produce the same results. The practical difference:
- nrel-spa is synchronous, has no loading delay, and is simpler to use in most contexts
- solar-spa is asynchronous (WASM initialization), but can achieve higher throughput for batch calculations (219,000 calls/sec for zenith-only vs. nrel-spa's ~100,000/sec)
For single-call or per-request use cases, nrel-spa is the better choice. For batch pre-computation of thousands of time steps, solar-spa's WASM throughput becomes relevant.