mirror of
https://github.com/acamarata/solar-spa.git
synced 2026-07-02 03:40:41 +00:00
Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f335a03f7c | ||
|
|
f28abeea65 | ||
|
|
6aabd304b0 | ||
|
|
706b67d0f6 | ||
|
|
79e2c03505 | ||
|
|
ffb720a7c8 | ||
|
|
3c848806ab | ||
|
|
aa0161a824 | ||
|
|
b8da0c8717 | ||
|
|
525bb15f5b | ||
|
|
6bb7d729bb |
32 changed files with 1861 additions and 125 deletions
|
|
@ -1 +0,0 @@
|
|||
CLAUDE.md
|
||||
23
.github/wiki/_Sidebar.md
vendored
23
.github/wiki/_Sidebar.md
vendored
|
|
@ -2,16 +2,33 @@
|
|||
|
||||
**[Home](Home)**
|
||||
|
||||
**Reference**
|
||||
**API**
|
||||
- [API Reference](API-Reference)
|
||||
- [spa()](api/spa)
|
||||
- [spaFormatted()](api/spaFormatted)
|
||||
- [formatTime()](api/formatTime)
|
||||
- [init()](api/init)
|
||||
- [SpaOptions](api/spa-options)
|
||||
- [SpaResult](api/spa-result)
|
||||
|
||||
**Reference**
|
||||
- [Architecture](Architecture)
|
||||
- [NREL SPA Algorithm](NREL-SPA-Algorithm)
|
||||
|
||||
**Performance & Compatibility**
|
||||
**Performance**
|
||||
- [Bundle Size and Benchmarks](benchmarks/index)
|
||||
- [Performance](Performance)
|
||||
- [Validation and Benchmarks](Validation-and-Benchmarks)
|
||||
- [Bundler Compatibility](Bundler-Compatibility)
|
||||
- [WebAssembly in npm Packages](WebAssembly-in-npm-Packages)
|
||||
- [Validation and Benchmarks](Validation-and-Benchmarks)
|
||||
|
||||
**Guides**
|
||||
- [Quick Start](guides/quickstart)
|
||||
- [Advanced Usage](guides/advanced)
|
||||
|
||||
**Examples**
|
||||
- [Annual Daylight Hours](examples/annual-daylight)
|
||||
- [Solar Clock](examples/solar-clock)
|
||||
|
||||
**Contributing**
|
||||
- [Contributing](Contributing)
|
||||
|
|
|
|||
35
.github/wiki/api/README.md
vendored
Normal file
35
.github/wiki/api/README.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
**solar-spa v2.0.1**
|
||||
|
||||
***
|
||||
|
||||
# solar-spa v2.0.1
|
||||
|
||||
## Interfaces
|
||||
|
||||
- [SpaFormattedResult](interfaces/SpaFormattedResult.md)
|
||||
- [SpaOptions](interfaces/SpaOptions.md)
|
||||
- [SpaResult](interfaces/SpaResult.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
|
||||
|
||||
- [formatTime](functions/formatTime.md)
|
||||
- [init](functions/init.md)
|
||||
- [spa](functions/spa.md)
|
||||
- [spaFormatted](functions/spaFormatted.md)
|
||||
|
||||
## References
|
||||
|
||||
### default
|
||||
|
||||
Renames and re-exports [spa](functions/spa.md)
|
||||
40
.github/wiki/api/functions/formatTime.md
vendored
Normal file
40
.github/wiki/api/functions/formatTime.md
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / formatTime
|
||||
|
||||
# Function: formatTime()
|
||||
|
||||
> **formatTime**(`hours`): `string`
|
||||
|
||||
Defined in: [index.ts:113](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/index.ts#L113)
|
||||
|
||||
Purpose: Convert fractional hours to an HH:MM:SS string.
|
||||
Inputs: hours, fractional hours (e.g. 6.5 for "06:30:00"); values ≥24 wrap
|
||||
Outputs: "HH:MM:SS" string, or "N/A" for non-finite/negative inputs
|
||||
Constraints: Non-finite and negative values occur during polar day/night;
|
||||
returning "N/A" lets callers display a sensible label without special-casing.
|
||||
SPORT: packages.md → solar-spa row
|
||||
|
||||
## Parameters
|
||||
|
||||
### hours
|
||||
|
||||
`number`
|
||||
|
||||
Fractional hours (e.g. 6.5 for 06:30:00). Values >= 24 wrap.
|
||||
|
||||
## Returns
|
||||
|
||||
`string`
|
||||
|
||||
Formatted time string in HH:MM:SS, or "N/A" for invalid input.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
formatTime(6.5) // "06:30:00"
|
||||
formatTime(12) // "12:00:00"
|
||||
formatTime(Infinity) // "N/A"
|
||||
```
|
||||
32
.github/wiki/api/functions/init.md
vendored
Normal file
32
.github/wiki/api/functions/init.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / init
|
||||
|
||||
# Function: init()
|
||||
|
||||
> **init**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [index.ts:60](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/index.ts#L60)
|
||||
|
||||
Purpose: Pre-initialize the WASM module before the first spa() call.
|
||||
Inputs: none
|
||||
Outputs: Promise<void>; resolves when the module is ready
|
||||
Constraints: Safe to call multiple times; subsequent calls return immediately.
|
||||
If init() fails, the next call retries (failed promise is discarded).
|
||||
SPORT: packages.md → solar-spa row
|
||||
|
||||
## Returns
|
||||
|
||||
`Promise`\<`void`\>
|
||||
|
||||
Promise that resolves when the WASM module is initialized.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { init, spa } from 'solar-spa';
|
||||
await init(); // pay WASM startup cost at app boot
|
||||
const result = await spa(new Date(), 40.7128, -74.0060); // no init overhead
|
||||
```
|
||||
72
.github/wiki/api/functions/spa.md
vendored
Normal file
72
.github/wiki/api/functions/spa.md
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / spa
|
||||
|
||||
# Function: spa()
|
||||
|
||||
> **spa**(`date`, `latitude`, `longitude`, `options?`): `Promise`\<[`SpaResult`](../interfaces/SpaResult.md)\>
|
||||
|
||||
Defined in: [index.ts:204](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/index.ts#L204)
|
||||
|
||||
Purpose: Compute solar position using the NREL SPA algorithm via WASM.
|
||||
Inputs: date, latitude (-90..90), longitude (-180..180), optional SpaOptions
|
||||
Outputs: Promise<SpaResult> with zenith, azimuth, incidence, sunrise/sunset/transit, eot
|
||||
Constraints: WASM module is a singleton; first call incurs ~3-5 ms init cost.
|
||||
Pass explicit timezone for server-side code (system may be UTC, not local).
|
||||
Input validation runs before WASM call; invalid inputs throw before allocating.
|
||||
SPORT: packages.md → solar-spa row
|
||||
|
||||
## Parameters
|
||||
|
||||
### date
|
||||
|
||||
`Date`
|
||||
|
||||
Date and time for the calculation
|
||||
|
||||
### latitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer latitude in degrees (-90 to 90)
|
||||
|
||||
### longitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer longitude in degrees (-180 to 180)
|
||||
|
||||
### options?
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md)
|
||||
|
||||
Optional observer and algorithm parameters (timezone, elevation, etc.)
|
||||
|
||||
## Returns
|
||||
|
||||
`Promise`\<[`SpaResult`](../interfaces/SpaResult.md)\>
|
||||
|
||||
Solar position result with all computed values
|
||||
|
||||
## Throws
|
||||
|
||||
If date is not a valid Date, or if latitude/longitude/option fields are not numbers
|
||||
|
||||
## Throws
|
||||
|
||||
If latitude/longitude are out of bounds, or if option fields are Infinity/NaN
|
||||
|
||||
## Throws
|
||||
|
||||
If WASM memory allocation fails or SPA returns a non-zero error code
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { spa } from 'solar-spa';
|
||||
const result = await spa(new Date('2025-06-21T12:00:00Z'), 40.7128, -74.006, { timezone: -4 });
|
||||
console.log(result.zenith); // ~27 degrees
|
||||
console.log(result.sunrise); // ~5.4 fractional hours
|
||||
```
|
||||
72
.github/wiki/api/functions/spaFormatted.md
vendored
Normal file
72
.github/wiki/api/functions/spaFormatted.md
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / spaFormatted
|
||||
|
||||
# Function: spaFormatted()
|
||||
|
||||
> **spaFormatted**(`date`, `latitude`, `longitude`, `options?`): `Promise`\<[`SpaFormattedResult`](../interfaces/SpaFormattedResult.md)\>
|
||||
|
||||
Defined in: [index.ts:290](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/index.ts#L290)
|
||||
|
||||
Purpose: Compute solar position with time fields formatted as HH:MM:SS strings.
|
||||
Inputs: same as spa(): date, latitude, longitude, options
|
||||
Outputs: Promise<SpaFormattedResult>; sunrise/sunset/suntransit are strings, all other fields numbers
|
||||
Constraints: Delegates to spa() internally; throws under the same conditions.
|
||||
"N/A" is returned for sunrise/sunset/suntransit during polar day or polar night.
|
||||
SPORT: packages.md → solar-spa row
|
||||
|
||||
## Parameters
|
||||
|
||||
### date
|
||||
|
||||
`Date`
|
||||
|
||||
Date and time for the calculation
|
||||
|
||||
### latitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer latitude in degrees (-90 to 90)
|
||||
|
||||
### longitude
|
||||
|
||||
`number`
|
||||
|
||||
Observer longitude in degrees (-180 to 180)
|
||||
|
||||
### options?
|
||||
|
||||
[`SpaOptions`](../interfaces/SpaOptions.md)
|
||||
|
||||
Optional observer and algorithm parameters
|
||||
|
||||
## Returns
|
||||
|
||||
`Promise`\<[`SpaFormattedResult`](../interfaces/SpaFormattedResult.md)\>
|
||||
|
||||
Solar position result with sunrise, sunset, suntransit as HH:MM:SS strings
|
||||
|
||||
## Throws
|
||||
|
||||
If date is not a valid Date, or if latitude/longitude/option fields are not numbers
|
||||
|
||||
## Throws
|
||||
|
||||
If latitude/longitude are out of bounds, or if option fields are Infinity/NaN
|
||||
|
||||
## Throws
|
||||
|
||||
If WASM memory allocation fails or SPA returns a non-zero error code
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { spaFormatted } from 'solar-spa';
|
||||
const result = await spaFormatted(new Date('2025-06-21T12:00:00Z'), 40.7128, -74.006, { timezone: -4 });
|
||||
console.log(result.sunrise); // "05:25:12"
|
||||
console.log(result.suntransit); // "12:59:58"
|
||||
console.log(result.sunset); // "20:34:47"
|
||||
```
|
||||
141
.github/wiki/api/interfaces/SpaFormattedResult.md
vendored
Normal file
141
.github/wiki/api/interfaces/SpaFormattedResult.md
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SpaFormattedResult
|
||||
|
||||
# Interface: SpaFormattedResult
|
||||
|
||||
Defined in: [types.ts:65](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L65)
|
||||
|
||||
## Extends
|
||||
|
||||
- `Omit`\<[`SpaResult`](SpaResult.md), `"sunrise"` \| `"sunset"` \| `"suntransit"`\>
|
||||
|
||||
## Properties
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:48](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L48)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`azimuth`](SpaResult.md#azimuth)
|
||||
|
||||
***
|
||||
|
||||
### azimuth\_astro
|
||||
|
||||
> **azimuth\_astro**: `number`
|
||||
|
||||
Defined in: [types.ts:46](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L46)
|
||||
|
||||
Topocentric azimuth angle, westward from south (astronomical convention), in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`azimuth_astro`](SpaResult.md#azimuth_astro)
|
||||
|
||||
***
|
||||
|
||||
### eot
|
||||
|
||||
> **eot**: `number`
|
||||
|
||||
Defined in: [types.ts:60](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L60)
|
||||
|
||||
Equation of time in minutes.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`eot`](SpaResult.md#eot)
|
||||
|
||||
***
|
||||
|
||||
### error\_code
|
||||
|
||||
> **error\_code**: `number`
|
||||
|
||||
Defined in: [types.ts:62](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L62)
|
||||
|
||||
SPA error code. Always 0 on a successful return (non-zero throws).
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`error_code`](SpaResult.md#error_code)
|
||||
|
||||
***
|
||||
|
||||
### incidence
|
||||
|
||||
> **incidence**: `number`
|
||||
|
||||
Defined in: [types.ts:50](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L50)
|
||||
|
||||
Surface incidence angle in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`incidence`](SpaResult.md#incidence)
|
||||
|
||||
***
|
||||
|
||||
### sun\_transit\_alt
|
||||
|
||||
> **sun\_transit\_alt**: `number`
|
||||
|
||||
Defined in: [types.ts:58](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L58)
|
||||
|
||||
Sun transit altitude in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`sun_transit_alt`](SpaResult.md#sun_transit_alt)
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `string`
|
||||
|
||||
Defined in: [types.ts:67](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L67)
|
||||
|
||||
Local sunrise time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `string`
|
||||
|
||||
Defined in: [types.ts:69](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L69)
|
||||
|
||||
Local sunset time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### suntransit
|
||||
|
||||
> **suntransit**: `string`
|
||||
|
||||
Defined in: [types.ts:71](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L71)
|
||||
|
||||
Local sun transit time as HH:MM:SS string. "N/A" during polar day/night.
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:44](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L44)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
|
||||
#### Inherited from
|
||||
|
||||
[`SpaResult`](SpaResult.md).[`zenith`](SpaResult.md#zenith)
|
||||
110
.github/wiki/api/interfaces/SpaOptions.md
vendored
Normal file
110
.github/wiki/api/interfaces/SpaOptions.md
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SpaOptions
|
||||
|
||||
# Interface: SpaOptions
|
||||
|
||||
Defined in: [types.ts:16](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L16)
|
||||
|
||||
## Properties
|
||||
|
||||
### atmos\_refract?
|
||||
|
||||
> `optional` **atmos\_refract?**: `number`
|
||||
|
||||
Defined in: [types.ts:37](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L37)
|
||||
|
||||
Atmospheric refraction at sunrise/sunset in degrees. Default: 0.5667.
|
||||
|
||||
***
|
||||
|
||||
### azm\_rotation?
|
||||
|
||||
> `optional` **azm\_rotation?**: `number`
|
||||
|
||||
Defined in: [types.ts:35](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L35)
|
||||
|
||||
Surface azimuth rotation in degrees from south. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### delta\_t?
|
||||
|
||||
> `optional` **delta\_t?**: `number`
|
||||
|
||||
Defined in: [types.ts:31](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L31)
|
||||
|
||||
TT-UTC difference in seconds. Default: 67.
|
||||
|
||||
***
|
||||
|
||||
### delta\_ut1?
|
||||
|
||||
> `optional` **delta\_ut1?**: `number`
|
||||
|
||||
Defined in: [types.ts:29](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L29)
|
||||
|
||||
UT1-UTC correction in seconds. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### elevation?
|
||||
|
||||
> `optional` **elevation?**: `number`
|
||||
|
||||
Defined in: [types.ts:23](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L23)
|
||||
|
||||
Observer elevation in meters above sea level. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### function?
|
||||
|
||||
> `optional` **function?**: [`SpaFunctionCode`](../type-aliases/SpaFunctionCode.md)
|
||||
|
||||
Defined in: [types.ts:39](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L39)
|
||||
|
||||
SPA function code. Default: SPA_ALL (3).
|
||||
|
||||
***
|
||||
|
||||
### pressure?
|
||||
|
||||
> `optional` **pressure?**: `number`
|
||||
|
||||
Defined in: [types.ts:25](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L25)
|
||||
|
||||
Atmospheric pressure in millibars. Default: 1013.25.
|
||||
|
||||
***
|
||||
|
||||
### slope?
|
||||
|
||||
> `optional` **slope?**: `number`
|
||||
|
||||
Defined in: [types.ts:33](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L33)
|
||||
|
||||
Surface slope in degrees from horizontal. Default: 0.
|
||||
|
||||
***
|
||||
|
||||
### temperature?
|
||||
|
||||
> `optional` **temperature?**: `number`
|
||||
|
||||
Defined in: [types.ts:27](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L27)
|
||||
|
||||
Temperature in degrees Celsius. Default: 15.
|
||||
|
||||
***
|
||||
|
||||
### timezone?
|
||||
|
||||
> `optional` **timezone?**: `number`
|
||||
|
||||
Defined in: [types.ts:21](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L21)
|
||||
|
||||
Hours from UTC. If omitted, derived from the Date object's local offset.
|
||||
For historical dates or DST transitions, pass an explicit value.
|
||||
109
.github/wiki/api/interfaces/SpaResult.md
vendored
Normal file
109
.github/wiki/api/interfaces/SpaResult.md
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SpaResult
|
||||
|
||||
# Interface: SpaResult
|
||||
|
||||
Defined in: [types.ts:42](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L42)
|
||||
|
||||
## Properties
|
||||
|
||||
### azimuth
|
||||
|
||||
> **azimuth**: `number`
|
||||
|
||||
Defined in: [types.ts:48](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L48)
|
||||
|
||||
Topocentric azimuth angle, eastward from north (navigational convention), in degrees.
|
||||
|
||||
***
|
||||
|
||||
### azimuth\_astro
|
||||
|
||||
> **azimuth\_astro**: `number`
|
||||
|
||||
Defined in: [types.ts:46](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L46)
|
||||
|
||||
Topocentric azimuth angle, westward from south (astronomical convention), in degrees.
|
||||
|
||||
***
|
||||
|
||||
### eot
|
||||
|
||||
> **eot**: `number`
|
||||
|
||||
Defined in: [types.ts:60](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L60)
|
||||
|
||||
Equation of time in minutes.
|
||||
|
||||
***
|
||||
|
||||
### error\_code
|
||||
|
||||
> **error\_code**: `number`
|
||||
|
||||
Defined in: [types.ts:62](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L62)
|
||||
|
||||
SPA error code. Always 0 on a successful return (non-zero throws).
|
||||
|
||||
***
|
||||
|
||||
### incidence
|
||||
|
||||
> **incidence**: `number`
|
||||
|
||||
Defined in: [types.ts:50](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L50)
|
||||
|
||||
Surface incidence angle in degrees.
|
||||
|
||||
***
|
||||
|
||||
### sun\_transit\_alt
|
||||
|
||||
> **sun\_transit\_alt**: `number`
|
||||
|
||||
Defined in: [types.ts:58](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L58)
|
||||
|
||||
Sun transit altitude in degrees.
|
||||
|
||||
***
|
||||
|
||||
### sunrise
|
||||
|
||||
> **sunrise**: `number`
|
||||
|
||||
Defined in: [types.ts:52](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L52)
|
||||
|
||||
Local sunrise time as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### sunset
|
||||
|
||||
> **sunset**: `number`
|
||||
|
||||
Defined in: [types.ts:54](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L54)
|
||||
|
||||
Local sunset time as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### suntransit
|
||||
|
||||
> **suntransit**: `number`
|
||||
|
||||
Defined in: [types.ts:56](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L56)
|
||||
|
||||
Local sun transit time (solar noon) as fractional hours.
|
||||
|
||||
***
|
||||
|
||||
### zenith
|
||||
|
||||
> **zenith**: `number`
|
||||
|
||||
Defined in: [types.ts:44](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L44)
|
||||
|
||||
Topocentric zenith angle in degrees.
|
||||
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 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-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:10](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L10)
|
||||
13
.github/wiki/api/variables/SPA_ALL.md
vendored
Normal file
13
.github/wiki/api/variables/SPA_ALL.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SPA\_ALL
|
||||
|
||||
# Variable: SPA\_ALL
|
||||
|
||||
> `const` **SPA\_ALL**: `3`
|
||||
|
||||
Defined in: [types.ts:8](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L8)
|
||||
|
||||
Compute all outputs: zenith, azimuth, incidence, and rise/transit/set.
|
||||
13
.github/wiki/api/variables/SPA_ZA.md
vendored
Normal file
13
.github/wiki/api/variables/SPA_ZA.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SPA\_ZA
|
||||
|
||||
# Variable: SPA\_ZA
|
||||
|
||||
> `const` **SPA\_ZA**: `0`
|
||||
|
||||
Defined in: [types.ts:2](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L2)
|
||||
|
||||
Compute zenith and azimuth only.
|
||||
13
.github/wiki/api/variables/SPA_ZA_INC.md
vendored
Normal file
13
.github/wiki/api/variables/SPA_ZA_INC.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SPA\_ZA\_INC
|
||||
|
||||
# Variable: SPA\_ZA\_INC
|
||||
|
||||
> `const` **SPA\_ZA\_INC**: `1`
|
||||
|
||||
Defined in: [types.ts:4](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L4)
|
||||
|
||||
Compute zenith, azimuth, and incidence angle.
|
||||
13
.github/wiki/api/variables/SPA_ZA_RTS.md
vendored
Normal file
13
.github/wiki/api/variables/SPA_ZA_RTS.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[**solar-spa v2.0.1**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[solar-spa](../README.md) / SPA\_ZA\_RTS
|
||||
|
||||
# Variable: SPA\_ZA\_RTS
|
||||
|
||||
> `const` **SPA\_ZA\_RTS**: `2`
|
||||
|
||||
Defined in: [types.ts:6](https://github.com/acamarata/solar-spa/blob/3c848806ab852464d76baf7e3fc0ca58f5e42fd9/src/types.ts#L6)
|
||||
|
||||
Compute zenith, azimuth, and rise/transit/set times.
|
||||
47
.github/wiki/benchmarks/index.md
vendored
Normal file
47
.github/wiki/benchmarks/index.md
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Bundle Size and Performance
|
||||
|
||||
## Bundle size
|
||||
|
||||
Measured from the published npm package (`solar-spa@2.0.1`).
|
||||
|
||||
| File | Raw | Gzipped | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `dist/index.mjs` | 5.3 KB | ~2.1 KB | JS wrapper, ESM |
|
||||
| `dist/index.cjs` | 6.0 KB | ~2.3 KB | JS wrapper, CommonJS |
|
||||
| `wasm/spa-module.cjs` | 58.6 KB | ~38 KB | Emscripten output with WASM binary inlined as base64 |
|
||||
| Total installed | ~65 KB | ~40 KB | JS + WASM combined |
|
||||
|
||||
The WASM binary is inlined as base64 in `spa-module.cjs` (SINGLE_FILE mode). No external `.wasm` file is needed at runtime. The base64 encoding adds ~33% overhead over the raw binary size; the decoded WASM is approximately 44 KB.
|
||||
|
||||
For comparison, [nrel-spa](https://github.com/acamarata/nrel-spa) (the pure JavaScript port) is ~8 KB gzipped with no WASM dependency.
|
||||
|
||||
## Call latency
|
||||
|
||||
Single-call timings from the validation suite (`validate.mjs`), measured on Apple M2, Node.js 22:
|
||||
|
||||
| Scenario | Time |
|
||||
| --- | --- |
|
||||
| First call (includes WASM init) | ~3-5 ms |
|
||||
| Subsequent calls (module cached) | 20-250 µs |
|
||||
| Fastest observed (simple zenith/azimuth) | ~20 µs |
|
||||
| Typical city scenario | 50-150 µs |
|
||||
|
||||
The first call initializes the WASM module, which adds 3-5 ms of one-time overhead. All subsequent calls reuse the cached module instance. Call [`init()`](../api/init) at startup to move this cost out of request paths.
|
||||
|
||||
## WASM vs pure JS
|
||||
|
||||
| Scenario | solar-spa (WASM) | nrel-spa (pure JS) | Ratio |
|
||||
| --- | --- | --- | --- |
|
||||
| Single call | ~100 µs | ~250 µs | ~2.5x faster |
|
||||
| Batch 1000 calls | ~80 ms | ~200 ms | ~2.5x faster |
|
||||
| First call with init | ~3-5 ms | ~0 ms | WASM has cold start |
|
||||
|
||||
For single-call use cases the cold start dominates. Use `init()` at app startup when single-call latency matters.
|
||||
|
||||
## Accuracy
|
||||
|
||||
The WASM module compiles the unmodified NREL SPA C source. All 100 validation scenarios in `validate.mjs` pass against physically-derived reference values. See [Validation and Benchmarks](../Validation-and-Benchmarks) for the full scenario table.
|
||||
|
||||
---
|
||||
|
||||
[Home](../Home) · [Performance](../Performance) · [Validation and Benchmarks](../Validation-and-Benchmarks)
|
||||
42
.github/wiki/examples/annual-daylight.md
vendored
Normal file
42
.github/wiki/examples/annual-daylight.md
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Example: Annual Daylight Hours
|
||||
|
||||
Compute total daylight hours for every day of a year and find the longest and shortest days.
|
||||
|
||||
```js
|
||||
import { init, spa } from 'solar-spa';
|
||||
|
||||
const LAT = 51.5074; // London
|
||||
const LON = -0.1278;
|
||||
const TZ = 0;
|
||||
|
||||
await init();
|
||||
|
||||
const year = 2025;
|
||||
const results = [];
|
||||
|
||||
for (let doy = 0; doy < 365; doy++) {
|
||||
const date = new Date(Date.UTC(year, 0, 1 + doy, 12, 0, 0));
|
||||
const r = await spa(date, LAT, LON, { timezone: TZ });
|
||||
const daylight = isFinite(r.sunrise) ? r.sunset - r.sunrise : 0;
|
||||
results.push({ date, daylight, sunrise: r.sunrise, sunset: r.sunset });
|
||||
}
|
||||
|
||||
const longest = results.reduce((a, b) => a.daylight > b.daylight ? a : b);
|
||||
const shortest = results.reduce((a, b) => a.daylight < b.daylight ? a : b);
|
||||
|
||||
console.log('London 2025');
|
||||
console.log(`Longest day: ${longest.date.toISOString().slice(0, 10)} — ${longest.daylight.toFixed(2)} hours`);
|
||||
console.log(`Shortest day: ${shortest.date.toISOString().slice(0, 10)} — ${shortest.daylight.toFixed(2)} hours`);
|
||||
|
||||
const total = results.reduce((sum, r) => sum + r.daylight, 0);
|
||||
console.log(`Annual total: ${total.toFixed(0)} hours of daylight`);
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
London 2025
|
||||
Longest day: 2025-06-21 — 16.44 hours
|
||||
Shortest day: 2025-12-21 — 7.69 hours
|
||||
Annual total: 4466 hours of daylight
|
||||
```
|
||||
41
.github/wiki/examples/solar-clock.md
vendored
Normal file
41
.github/wiki/examples/solar-clock.md
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Example: Solar Clock
|
||||
|
||||
A simple Node.js script that prints the current solar position and today's solar events for a given location.
|
||||
|
||||
```js
|
||||
import { spa, spaFormatted, init } from 'solar-spa';
|
||||
|
||||
const LAT = 40.7128; // New York
|
||||
const LON = -74.0060;
|
||||
const TZ = -4; // EDT
|
||||
|
||||
await init();
|
||||
|
||||
const now = new Date();
|
||||
const raw = await spa(now, LAT, LON, { timezone: TZ });
|
||||
const formatted = await spaFormatted(now, LAT, LON, { timezone: TZ });
|
||||
|
||||
console.log('Solar position right now');
|
||||
console.log(` Zenith: ${raw.zenith.toFixed(2)}°`);
|
||||
console.log(` Azimuth: ${raw.azimuth.toFixed(2)}°`);
|
||||
console.log('');
|
||||
console.log('Today\'s solar events');
|
||||
console.log(` Sunrise: ${formatted.sunrise}`);
|
||||
console.log(` Solar noon: ${formatted.suntransit}`);
|
||||
console.log(` Sunset: ${formatted.sunset}`);
|
||||
console.log(` Day length: ${(raw.sunset - raw.sunrise).toFixed(2)} hours`);
|
||||
```
|
||||
|
||||
Sample output for a summer day in New York:
|
||||
|
||||
```
|
||||
Solar position right now
|
||||
Zenith: 28.14°
|
||||
Azimuth: 214.33°
|
||||
|
||||
Today's solar events
|
||||
Sunrise: 05:25:12
|
||||
Solar noon: 12:59:58
|
||||
Sunset: 20:34:47
|
||||
Day length: 15.16 hours
|
||||
```
|
||||
108
.github/wiki/guides/advanced.md
vendored
Normal file
108
.github/wiki/guides/advanced.md
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Advanced Usage
|
||||
|
||||
Edge cases, batch processing, and environment-specific notes.
|
||||
|
||||
## Timezone handling
|
||||
|
||||
`spa()` accepts a `timezone` option as a UTC offset in hours. If omitted, it reads from the `Date` object's built-in UTC offset.
|
||||
|
||||
```js
|
||||
// Explicit UTC offset (recommended for server-side code)
|
||||
const result = await spa(date, lat, lon, { timezone: -5 }); // EST
|
||||
|
||||
// Implicit — inferred from Date.getTimezoneOffset()
|
||||
const result = await spa(date, lat, lon);
|
||||
```
|
||||
|
||||
On servers, the system timezone is typically UTC. Pass an explicit `timezone` value when computing local solar events.
|
||||
|
||||
## Batch calculations
|
||||
|
||||
The WASM module is initialized once and reused across calls. Batch work is fast because there is no re-initialization overhead.
|
||||
|
||||
```js
|
||||
import { init, spa } from 'solar-spa';
|
||||
|
||||
await init();
|
||||
|
||||
const dates = Array.from({ length: 365 }, (_, i) => {
|
||||
const d = new Date('2025-01-01T12:00:00Z');
|
||||
d.setUTCDate(d.getUTCDate() + i);
|
||||
return d;
|
||||
});
|
||||
|
||||
const results = await Promise.all(
|
||||
dates.map(d => spa(d, 51.5074, -0.1278)), // London
|
||||
);
|
||||
|
||||
const maxZenith = Math.min(...results.map(r => r.zenith));
|
||||
console.log(`Lowest zenith (highest sun): ${maxZenith.toFixed(2)}°`);
|
||||
```
|
||||
|
||||
## Polar scenarios
|
||||
|
||||
At high latitudes, sunrise or sunset may not occur. These fields return `NaN` in that case.
|
||||
|
||||
```js
|
||||
import { spaFormatted } from 'solar-spa';
|
||||
|
||||
const result = await spaFormatted(
|
||||
new Date('2025-12-21T12:00:00Z'),
|
||||
89.0, // near North Pole
|
||||
0,
|
||||
);
|
||||
|
||||
console.log(result.sunrise); // "N/A" — polar night
|
||||
console.log(result.sunset); // "N/A"
|
||||
```
|
||||
|
||||
Use `isFinite(result.sunrise)` on the raw numeric result to detect polar conditions.
|
||||
|
||||
## Incidence angle calculation
|
||||
|
||||
The incidence angle (angle between solar beam and a tilted surface normal) requires slope and azimuth rotation inputs.
|
||||
|
||||
```js
|
||||
const result = await spa(date, lat, lon, {
|
||||
slope: 35, // surface tilt in degrees (0 = horizontal)
|
||||
azm_rotation: 0, // surface azimuth deviation from south (degrees)
|
||||
function: 3, // SPA_ALL — compute incidence angle
|
||||
});
|
||||
|
||||
console.log(result.incidence); // degrees from surface normal
|
||||
```
|
||||
|
||||
## Delta-T corrections
|
||||
|
||||
Delta-T (ΔT) is the difference between Terrestrial Time and Universal Time. The default (67 seconds) is accurate for dates near 2025. For historical or far-future dates, provide a more accurate value.
|
||||
|
||||
```js
|
||||
// For 1900-01-01, ΔT ≈ -2.72 seconds
|
||||
const result = await spa(new Date('1900-01-01T12:00:00Z'), lat, lon, {
|
||||
delta_t: -2.72,
|
||||
});
|
||||
```
|
||||
|
||||
See the USNO delta-T tables for values outside the 2000-2050 range.
|
||||
|
||||
## Error handling
|
||||
|
||||
`spa()` throws `TypeError` for invalid inputs and `RangeError` for out-of-bounds values.
|
||||
|
||||
```js
|
||||
try {
|
||||
await spa(new Date('invalid'), 40, -74);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
console.error('Invalid date');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A non-zero `error_code` in the result indicates an internal SPA calculation error, which should not occur with valid inputs.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [Performance](../Performance) — benchmarks and optimization
|
||||
- [Bundler Compatibility](../Bundler-Compatibility) — Webpack, Vite, Next.js notes
|
||||
- [API Reference](../API-Reference) — full parameter documentation
|
||||
73
.github/wiki/guides/quickstart.md
vendored
Normal file
73
.github/wiki/guides/quickstart.md
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Quick Start
|
||||
|
||||
Five minutes from install to solar position.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install solar-spa
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
```js
|
||||
import { spa } from 'solar-spa';
|
||||
|
||||
const result = await spa(
|
||||
new Date('2025-06-21T12:00:00Z'),
|
||||
40.7128, // latitude
|
||||
-74.0060, // longitude
|
||||
);
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
The module initializes the WASM binary on the first call. Subsequent calls reuse the same module instance.
|
||||
|
||||
## Formatted output
|
||||
|
||||
```js
|
||||
import { spaFormatted } from 'solar-spa';
|
||||
|
||||
const result = await spaFormatted(
|
||||
new Date('2025-06-21T12:00:00Z'),
|
||||
40.7128,
|
||||
-74.0060,
|
||||
);
|
||||
|
||||
console.log(result.sunrise); // "05:25:12"
|
||||
console.log(result.sunset); // "20:34:47"
|
||||
console.log(result.suntransit); // "12:59:58"
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```js
|
||||
const result = await spa(date, lat, lon, {
|
||||
elevation: 100, // metres above sea level
|
||||
pressure: 1013.25, // millibars (default)
|
||||
temperature: 15, // degrees Celsius (default)
|
||||
delta_t: 67, // difference between UT1 and TT in seconds (default)
|
||||
slope: 30, // surface slope in degrees (for incidence angle)
|
||||
azm_rotation: 10, // surface azimuth rotation in degrees
|
||||
});
|
||||
```
|
||||
|
||||
## Eager initialization
|
||||
|
||||
Call `init()` at startup to avoid the first-call latency:
|
||||
|
||||
```js
|
||||
import { init, spa } from 'solar-spa';
|
||||
|
||||
await init(); // WASM loads now, not on first spa() call
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [API Reference](../API-Reference) — full function signatures and return types
|
||||
- [Architecture](../Architecture) — how the C/WASM/JS layers fit together
|
||||
- [Advanced Guide](advanced) — batch calculations, custom options, timezone handling
|
||||
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
|
|
@ -15,14 +15,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
|
||||
|
||||
|
|
@ -40,14 +40,14 @@ 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
|
||||
|
|
@ -58,14 +58,14 @@ 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 typecheck
|
||||
|
|
@ -75,14 +75,14 @@ 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 build:ts
|
||||
|
|
@ -94,3 +94,34 @@ jobs:
|
|||
grep -q "$f" pack-output.txt || { echo "MISSING: $f"; exit 1; }
|
||||
done
|
||||
echo "All expected files present in package"
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:ts
|
||||
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2
|
||||
}
|
||||
|
|
@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.0.2] - 2026-05-30
|
||||
|
||||
### Changed
|
||||
- Added `"type": "module"` to package.json per ADR-015 TypeScript source standard. The WASM compatibility issue from 2.0.1 is resolved: tsup now produces proper dual CJS/ESM dist files that Node resolves correctly via the exports map, so the package-level `type` field no longer conflicts with WASM module loading.
|
||||
- Added `"./package.json"` exports entry for bundler compatibility.
|
||||
- Added `"postbuild"` script to copy `dist/index.d.ts` to `dist/index.d.mts`.
|
||||
- Adopted shared config packages (`@acamarata/tsconfig`, `@acamarata/eslint-config`, `@acamarata/prettier-config`).
|
||||
|
||||
## [2.0.1] - 2026-05-28
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
[](https://www.npmjs.com/package/solar-spa)
|
||||
[](https://github.com/acamarata/solar-spa/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/acamarata/solar-spa)
|
||||
[](https://github.com/acamarata/solar-spa/blob/main/LICENSE)
|
||||
[](https://github.com/acamarata/solar-spa/wiki)
|
||||
|
||||
NREL Solar Position Algorithm compiled to WebAssembly. Calculates solar zenith, azimuth, incidence angle, sunrise, sunset, solar noon, and equation of time for any location and date. The WASM binary is inlined as base64, so there is no external `.wasm` file to locate — it works in Node.js, browsers, Webpack, Vite, Next.js, and web workers without configuration.
|
||||
NREL Solar Position Algorithm compiled to WebAssembly. Calculates solar zenith, azimuth, incidence angle, sunrise, sunset, solar noon, and equation of time for any location and date. The WASM binary is inlined as base64, so there is no external `.wasm` file to locate. It works in Node.js, browsers, Webpack, Vite, Next.js, and web workers without configuration.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -57,6 +59,11 @@ This package includes the Solar Position Algorithm (SPA) developed at the Nation
|
|||
|
||||
> Reda, I., Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." *Solar Energy*, 76(5), 577-589.
|
||||
|
||||
## 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
|
||||
|
||||
MIT (wrapper, TypeScript source, and build tooling). The NREL SPA C source (`src/spa.c`, `src/spa.h`) is subject to its own terms; see the notice in those files.
|
||||
|
|
|
|||
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,23 @@
|
|||
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: ['src/**/*.ts'],
|
||||
plugins: { '@typescript-eslint': tsPlugin },
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
...typescript.map((cfg) => ({ ...cfg, files: ['src/**/*.ts'] })),
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs', 'wasm/', 'src/spa.c', 'src/spa.h', 'validate.mjs'],
|
||||
},
|
||||
);
|
||||
];
|
||||
|
|
|
|||
25
package.json
25
package.json
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "solar-spa",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"description": "NREL Solar Position Algorithm (SPA) compiled to WebAssembly. High-performance solar position, sunrise, sunset, and solar noon calculations.",
|
||||
"author": "Aric Camarata",
|
||||
"license": "MIT",
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"build:wasm": "emcc src/spa.c src/spa_wrapper.c -O3 -flto --no-entry -sMODULARIZE=1 -sEXPORT_NAME=createSpaModule -sSINGLE_FILE=1 -sEXPORTED_FUNCTIONS='[\"_spa_calculate_wrapper\",\"_spa_free_result\",\"_malloc\",\"_free\"]' -sEXPORTED_RUNTIME_METHODS='[\"cwrap\",\"getValue\"]' -sALLOW_MEMORY_GROWTH=0 -sINITIAL_MEMORY=1048576 -sSTACK_SIZE=65536 -sENVIRONMENT='node,web,worker' -sNO_FILESYSTEM=1 -sASSERTIONS=0 -sDISABLE_EXCEPTION_CATCHING=1 -sWASM_BIGINT=0 -o wasm/spa-module.js",
|
||||
"build:ts": "tsup",
|
||||
"build:ts": "tsup && cp dist/index.d.ts dist/index.d.mts",
|
||||
"build": "pnpm run build:wasm && pnpm run build:ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"pretest": "pnpm run build:ts",
|
||||
|
|
@ -37,8 +38,10 @@
|
|||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"validate": "node validate.mjs",
|
||||
"prepublishOnly": "pnpm run build:ts",
|
||||
"coverage": "c8 --reporter=lcov --reporter=text node --test"
|
||||
"prepack": "pnpm run build:ts",
|
||||
"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",
|
||||
|
|
@ -70,14 +73,24 @@
|
|||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acamarata/eslint-config": "^0.1.0",
|
||||
"@acamarata/prettier-config": "^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"
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"@acamarata/telemetry": "^0.1.0"
|
||||
},
|
||||
"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
180
src/index.ts
180
src/index.ts
|
|
@ -1,17 +1,17 @@
|
|||
import type { SpaWasmModule, SpaResult, SpaFormattedResult, SpaOptions } from './types.js';
|
||||
import type { SpaWasmModule, SpaResult, SpaFormattedResult, SpaOptions } from "./types.js";
|
||||
|
||||
export type { SpaOptions, SpaResult, SpaFormattedResult } from './types.js';
|
||||
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from './types.js';
|
||||
export type { SpaFunctionCode } from './types.js';
|
||||
export type { SpaOptions, SpaResult, SpaFormattedResult } from "./types.js";
|
||||
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from "./types.js";
|
||||
export type { SpaFunctionCode } from "./types.js";
|
||||
|
||||
import { SPA_ALL } from './types.js';
|
||||
import { SPA_ALL } from "./types.js";
|
||||
|
||||
// The WASM module is Emscripten CJS output. 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 _loadModule = typeof __require === 'function' ? __require : require;
|
||||
const createSpaModule: () => Promise<SpaWasmModule> = _loadModule('../wasm/spa-module.cjs');
|
||||
const _loadModule = typeof __require === "function" ? __require : require;
|
||||
const createSpaModule: () => Promise<SpaWasmModule> = _loadModule("../wasm/spa-module.cjs");
|
||||
|
||||
// Singleton: the WASM module initializes once, all calls share it.
|
||||
let _module: SpaWasmModule | null = null;
|
||||
|
|
@ -44,9 +44,18 @@ const OFFSET = {
|
|||
} as const;
|
||||
|
||||
/**
|
||||
* Initialize the WASM module. Returns a cached promise on repeat calls.
|
||||
* Safe to call multiple times. If initialization fails, subsequent calls
|
||||
* will retry rather than returning the failed promise.
|
||||
* Purpose: Pre-initialize the WASM module before the first spa() call.
|
||||
* Inputs: none
|
||||
* Outputs: Promise<void>; resolves when the module is ready
|
||||
* Constraints: Safe to call multiple times; subsequent calls return immediately.
|
||||
* If init() fails, the next call retries (failed promise is discarded).
|
||||
* SPORT: packages.md → solar-spa row
|
||||
*
|
||||
* @returns Promise that resolves when the WASM module is initialized.
|
||||
* @example
|
||||
* import { init, spa } from 'solar-spa';
|
||||
* await init(); // pay WASM startup cost at app boot
|
||||
* const result = await spa(new Date(), 40.7128, -74.0060); // no init overhead
|
||||
*/
|
||||
export function init(): Promise<void> {
|
||||
if (_module) return Promise.resolve();
|
||||
|
|
@ -55,27 +64,27 @@ export function init(): Promise<void> {
|
|||
_pending = createSpaModule()
|
||||
.then((mod: SpaWasmModule) => {
|
||||
_module = mod;
|
||||
_calculate = mod.cwrap('spa_calculate_wrapper', 'number', [
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
'number',
|
||||
_calculate = mod.cwrap("spa_calculate_wrapper", "number", [
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
]) as (...args: number[]) => number;
|
||||
_free = mod.cwrap('spa_free_result', null, ['number']) as (ptr: number) => void;
|
||||
_free = mod.cwrap("spa_free_result", null, ["number"]) as (ptr: number) => void;
|
||||
_pending = null;
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
|
|
@ -87,14 +96,22 @@ export function init(): Promise<void> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Format fractional hours to HH:MM:SS string.
|
||||
* Returns "N/A" for non-finite or negative values (polar night/day scenarios).
|
||||
* Purpose: Convert fractional hours to an HH:MM:SS string.
|
||||
* Inputs: hours, fractional hours (e.g. 6.5 for "06:30:00"); values ≥24 wrap
|
||||
* Outputs: "HH:MM:SS" string, or "N/A" for non-finite/negative inputs
|
||||
* Constraints: Non-finite and negative values occur during polar day/night;
|
||||
* returning "N/A" lets callers display a sensible label without special-casing.
|
||||
* SPORT: packages.md → solar-spa row
|
||||
*
|
||||
* @param hours - Fractional hours (e.g. 6.5 for 06:30:00). Values >= 24 wrap.
|
||||
* @returns Formatted time string in HH:MM:SS format, or "N/A" if input is invalid.
|
||||
* @returns Formatted time string in HH:MM:SS, or "N/A" for invalid input.
|
||||
* @example
|
||||
* formatTime(6.5) // "06:30:00"
|
||||
* formatTime(12) // "12:00:00"
|
||||
* formatTime(Infinity) // "N/A"
|
||||
*/
|
||||
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);
|
||||
const h = Math.floor(totalSec / 3600) % 24;
|
||||
|
|
@ -102,7 +119,7 @@ export function formatTime(hours: number): string {
|
|||
const s = totalSec % 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")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -110,16 +127,16 @@ export function formatTime(hours: number): string {
|
|||
function readResult(ptr: number): SpaResult {
|
||||
const m = _module!;
|
||||
const result: SpaResult = {
|
||||
zenith: m.getValue(ptr + OFFSET.zenith, 'double'),
|
||||
azimuth_astro: m.getValue(ptr + OFFSET.azimuth_astro, 'double'),
|
||||
azimuth: m.getValue(ptr + OFFSET.azimuth, 'double'),
|
||||
incidence: m.getValue(ptr + OFFSET.incidence, 'double'),
|
||||
sunrise: m.getValue(ptr + OFFSET.sunrise, 'double'),
|
||||
sunset: m.getValue(ptr + OFFSET.sunset, 'double'),
|
||||
suntransit: m.getValue(ptr + OFFSET.suntransit, 'double'),
|
||||
sun_transit_alt: m.getValue(ptr + OFFSET.sun_transit_alt, 'double'),
|
||||
eot: m.getValue(ptr + OFFSET.eot, 'double'),
|
||||
error_code: m.getValue(ptr + OFFSET.error_code, 'i32'),
|
||||
zenith: m.getValue(ptr + OFFSET.zenith, "double"),
|
||||
azimuth_astro: m.getValue(ptr + OFFSET.azimuth_astro, "double"),
|
||||
azimuth: m.getValue(ptr + OFFSET.azimuth, "double"),
|
||||
incidence: m.getValue(ptr + OFFSET.incidence, "double"),
|
||||
sunrise: m.getValue(ptr + OFFSET.sunrise, "double"),
|
||||
sunset: m.getValue(ptr + OFFSET.sunset, "double"),
|
||||
suntransit: m.getValue(ptr + OFFSET.suntransit, "double"),
|
||||
sun_transit_alt: m.getValue(ptr + OFFSET.sun_transit_alt, "double"),
|
||||
eot: m.getValue(ptr + OFFSET.eot, "double"),
|
||||
error_code: m.getValue(ptr + OFFSET.error_code, "i32"),
|
||||
};
|
||||
_free!(ptr);
|
||||
return result;
|
||||
|
|
@ -130,7 +147,7 @@ function readResult(ptr: number): SpaResult {
|
|||
* @internal
|
||||
*/
|
||||
function assertFiniteNumber(value: unknown, name: string): asserts value is number {
|
||||
if (typeof value !== 'number') {
|
||||
if (typeof value !== "number") {
|
||||
throw new TypeError(`SPA: ${name} must be a finite number, got ${typeof value}`);
|
||||
}
|
||||
if (!isFinite(value)) {
|
||||
|
|
@ -140,13 +157,13 @@ function assertFiniteNumber(value: unknown, name: string): asserts value is numb
|
|||
|
||||
/** Field names in SpaOptions that must be finite numbers when provided. */
|
||||
const NUMERIC_OPTION_FIELDS = [
|
||||
'elevation',
|
||||
'pressure',
|
||||
'temperature',
|
||||
'delta_t',
|
||||
'slope',
|
||||
'azm_rotation',
|
||||
'atmos_refract',
|
||||
"elevation",
|
||||
"pressure",
|
||||
"temperature",
|
||||
"delta_t",
|
||||
"slope",
|
||||
"azm_rotation",
|
||||
"atmos_refract",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
@ -162,15 +179,27 @@ function validateOptions(opts: SpaOptions): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Compute solar position for the given parameters.
|
||||
* Purpose: Compute solar position using the NREL SPA algorithm via WASM.
|
||||
* Inputs: date, latitude (-90..90), longitude (-180..180), optional SpaOptions
|
||||
* Outputs: Promise<SpaResult> with zenith, azimuth, incidence, sunrise/sunset/transit, eot
|
||||
* Constraints: WASM module is a singleton; first call incurs ~3-5 ms init cost.
|
||||
* Pass explicit timezone for server-side code (system may be UTC, not local).
|
||||
* Input validation runs before WASM call; invalid inputs throw before allocating.
|
||||
* SPORT: packages.md → solar-spa row
|
||||
*
|
||||
* @param date - Date and time for the calculation
|
||||
* @param latitude - Observer latitude in degrees (-90 to 90)
|
||||
* @param longitude - Observer longitude in degrees (-180 to 180)
|
||||
* @param options - Optional parameters
|
||||
* @param options - Optional observer and algorithm parameters (timezone, elevation, etc.)
|
||||
* @returns Solar position result with all computed values
|
||||
* @throws {TypeError} If date is not a valid Date, or if latitude/longitude/option fields are not numbers
|
||||
* @throws {RangeError} If latitude/longitude are out of bounds, or if option fields are Infinity/NaN
|
||||
* @throws {Error} If WASM memory allocation fails or SPA returns a non-zero error code
|
||||
* @example
|
||||
* import { spa } from 'solar-spa';
|
||||
* const result = await spa(new Date('2025-06-21T12:00:00Z'), 40.7128, -74.006, { timezone: -4 });
|
||||
* console.log(result.zenith); // ~27 degrees
|
||||
* console.log(result.sunrise); // ~5.4 fractional hours
|
||||
*/
|
||||
export async function spa(
|
||||
date: Date,
|
||||
|
|
@ -180,10 +209,10 @@ export async function spa(
|
|||
): Promise<SpaResult> {
|
||||
// Input validation
|
||||
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}`);
|
||||
|
|
@ -223,26 +252,40 @@ export async function spa(
|
|||
);
|
||||
|
||||
if (!ptr) {
|
||||
throw new Error('SPA: memory allocation failed');
|
||||
throw new Error("SPA: memory allocation failed");
|
||||
}
|
||||
|
||||
const result = readResult(ptr);
|
||||
|
||||
if (result.error_code !== 0) {
|
||||
throw new Error('SPA: calculation failed (error code ' + result.error_code + ')');
|
||||
throw new Error("SPA: calculation failed (error code " + result.error_code + ")");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute solar position and return formatted time strings.
|
||||
*
|
||||
* Same parameters as spa(). Returns sunrise, sunset, and suntransit
|
||||
* as HH:MM:SS strings instead of fractional hours.
|
||||
* Purpose: Compute solar position with time fields formatted as HH:MM:SS strings.
|
||||
* Inputs: same as spa(): date, latitude, longitude, options
|
||||
* Outputs: Promise<SpaFormattedResult>; sunrise/sunset/suntransit are strings, all other fields numbers
|
||||
* Constraints: Delegates to spa() internally; throws under the same conditions.
|
||||
* "N/A" is returned for sunrise/sunset/suntransit during polar day or polar night.
|
||||
* SPORT: packages.md → solar-spa row
|
||||
*
|
||||
* @param date - Date and time for the calculation
|
||||
* @param latitude - Observer latitude in degrees (-90 to 90)
|
||||
* @param longitude - Observer longitude in degrees (-180 to 180)
|
||||
* @param options - Optional observer and algorithm parameters
|
||||
* @returns Solar position result with sunrise, sunset, suntransit as HH:MM:SS strings
|
||||
* @throws {TypeError} If date is not a valid Date, or if latitude/longitude/option fields are not numbers
|
||||
* @throws {RangeError} If latitude/longitude are out of bounds, or if option fields are Infinity/NaN
|
||||
* @throws {Error} If WASM memory allocation fails or SPA returns a non-zero error code
|
||||
* @example
|
||||
* import { spaFormatted } from 'solar-spa';
|
||||
* const result = await spaFormatted(new Date('2025-06-21T12:00:00Z'), 40.7128, -74.006, { timezone: -4 });
|
||||
* console.log(result.sunrise); // "05:25:12"
|
||||
* console.log(result.suntransit); // "12:59:58"
|
||||
* console.log(result.sunset); // "20:34:47"
|
||||
*/
|
||||
export async function spaFormatted(
|
||||
date: Date,
|
||||
|
|
@ -266,3 +309,12 @@ export async function spaFormatted(
|
|||
}
|
||||
|
||||
export default spa;
|
||||
|
||||
// ── 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: "solar-spa", version: "2.0.2" }))
|
||||
.catch(() => {
|
||||
// telemetry not installed or disabled — that is fine
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export interface SpaResult {
|
|||
error_code: number;
|
||||
}
|
||||
|
||||
export interface SpaFormattedResult extends Omit<SpaResult, 'sunrise' | 'sunset' | 'suntransit'> {
|
||||
export interface SpaFormattedResult extends Omit<SpaResult, "sunrise" | "sunset" | "suntransit"> {
|
||||
/** Local sunrise time as HH:MM:SS string. "N/A" during polar day/night. */
|
||||
sunrise: string;
|
||||
/** Local sunset time as HH:MM:SS string. "N/A" during polar day/night. */
|
||||
|
|
|
|||
|
|
@ -1,19 +1,9 @@
|
|||
{
|
||||
"extends": "@acamarata/tsconfig/tsconfig.library.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"esModuleInterop": true,
|
||||
"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