first commit

This commit is contained in:
Ali Camarata 2023-03-31 23:26:40 -04:00
commit f10c64c5f1
10 changed files with 3427 additions and 0 deletions

2
.npmignore Normal file
View file

@ -0,0 +1,2 @@
src/
test.js

71
README.md Normal file
View file

@ -0,0 +1,71 @@
# solar-spa
The solar-spa package provides a Node.js module for calculating solar position and related parameters using the National Renewable Energy Laboratory (NREL) Solar Position Algorithm (SPA). This implementation uses WebAssembly (WASM) to achieve high performance and accuracy.
The SPA calculates the solar zenith angle, azimuth angle, incidence angle, sunrise time, sunset time, solar noon time, and sun transit altitude for a given date, time, and location.
## Installation
To install the solar-spa package, use the following command:
```sh
npm install solar-spa
```
## Usage
```
const spa = require('solar-spa');
// Define input parameters for a specific date, time, and location
const date = new Date(2023, 3, 1, 0, 0, 0); // April 1, 2023 at Midnight
const latitude = 40.7128; // Latitude of New York City, USA
const longitude = -74.0060; // Longitude of New York City, USA
// Optional input parameters (default values provided if not specified)
const elevation = 10; // Elevation in meters (approximately)
const temperature = 20; // Temperature (degrees Celsius)
const pressure = 1013.25; // Atmospheric pressure (millibars)
const refraction = 0.5667; // Atmospheric refraction (degrees)
// Call the 'spa' function and log the results
spa(date, latitude, longitude, elevation, temperature, pressure, refraction)
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
```
## Example Output
```
{
zenith: 132.82035808538367,
azimuth: 339.3841959764823,
incidence: 132.82035808538367,
sun_transit_alt: 53.91557045916343,
sunrise: 6.665306569794356,
solar_noon: 12.99818246332967,
sunset: 19.342862135890314
}
```
## Helper (function not included)
If you would like to translate the sunrise, solar_noon, or sunset to a normal time output you can convert fractional hours to formatted time string like below:
```
function formatTime(hours) {
const milliseconds = hours * 60 * 60 * 1000;
const date = new Date(milliseconds);
return date.toISOString().substr(11, 12);
}
```
# Repository
The source code for this package is available on GitHub: github.com/acamarata/solar-spa
# License
MIT

11
package.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "solar-spa",
"version": "1.0.0",
"description": "NREL Solar Position Algorithm (SPA) in WebAssembly",
"main": "solar-spa.js",
"scripts": {
"test": "node test.js"
},
"author": "Ali Camarata",
"license": "MIT"
}

61
solar-spa.js Normal file
View file

@ -0,0 +1,61 @@
const spaModule = require('./spa.js');
module.exports = function spa(
date,
latitude,
longitude,
elevation = 0,
temperature = 20,
pressure = 1013.25,
refraction = 0.5667
) {
return new Promise((resolve) => {
spaModule.onRuntimeInitialized = function () {
const spa_calculate = spaModule.cwrap(
'spa_calculate_wrapper',
'number',
[
'number', 'number', 'number', 'number', 'number',
'number', 'number', 'number', 'number', 'number',
'number', 'number', 'number', 'number', 'number'
]
);
const spa_free_result = spaModule.cwrap(
'spa_free_result',
null,
['number']
);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const timezone = -date.getTimezoneOffset() / 60;
const slope = 0;
const azm_rotation = 0;
const resultPtr = spa_calculate(
year, month, day, hour, minute, second, timezone,
latitude, longitude, elevation, pressure, temperature,
slope, azm_rotation, refraction
);
const result = {
zenith: spaModule.getValue(resultPtr, 'double'),
azimuth: spaModule.getValue(resultPtr + 8, 'double'),
incidence: spaModule.getValue(resultPtr + 16, 'double'),
sunrise: spaModule.getValue(resultPtr + 24, 'double'),
sunset: spaModule.getValue(resultPtr + 32, 'double'),
solar_noon: spaModule.getValue(resultPtr + 40, 'double'),
sun_transit_alt: spaModule.getValue(resultPtr + 48, 'double'),
};
spa_free_result(resultPtr);
resolve(result);
};
});
};

1790
spa.js Normal file

File diff suppressed because it is too large Load diff

BIN
spa.wasm Executable file

Binary file not shown.

1173
src/spa.c Normal file

File diff suppressed because it is too large Load diff

206
src/spa.h Normal file
View file

@ -0,0 +1,206 @@
/////////////////////////////////////////////
// HEADER FILE for SPA.C //
// //
// Solar Position Algorithm (SPA) //
// for //
// Solar Radiation Application //
// //
// May 12, 2003 //
// //
// Filename: SPA.H //
// //
// Afshin Michael Andreas //
// afshin_andreas@nrel.gov (303)384-6383 //
// //
// Measurement & Instrumentation Team //
// Solar Radiation Research Laboratory //
// National Renewable Energy Laboratory //
// 1617 Cole Blvd, Golden, CO 80401 //
/////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// //
// Usage: //
// //
// 1) In calling program, include this header file, //
// by adding this line to the top of file: //
// #include "spa.h" //
// //
// 2) In calling program, declare the SPA structure: //
// spa_data spa; //
// //
// 3) Enter the required input values into SPA structure //
// (input values listed in comments below) //
// //
// 4) Call the SPA calculate function and pass the SPA structure //
// (prototype is declared at the end of this header file): //
// spa_calculate(&spa); //
// //
// Selected output values (listed in comments below) will be //
// computed and returned in the passed SPA structure. Output //
// will based on function code selected from enumeration below. //
// //
// Note: A non-zero return code from spa_calculate() indicates that //
// one of the input values did not pass simple bounds tests. //
// The valid input ranges and return error codes are also //
// listed below. //
// //
////////////////////////////////////////////////////////////////////////
#ifndef __solar_position_algorithm_header
#define __solar_position_algorithm_header
//enumeration for function codes to select desired final outputs from SPA
enum {
SPA_ZA, //calculate zenith and azimuth
SPA_ZA_INC, //calculate zenith, azimuth, and incidence
SPA_ZA_RTS, //calculate zenith, azimuth, and sun rise/transit/set values
SPA_ALL, //calculate all SPA output values
};
typedef struct
{
//----------------------INPUT VALUES------------------------
int year; // 4-digit year, valid range: -2000 to 6000, error code: 1
int month; // 2-digit month, valid range: 1 to 12, error code: 2
int day; // 2-digit day, valid range: 1 to 31, error code: 3
int hour; // Observer local hour, valid range: 0 to 24, error code: 4
int minute; // Observer local minute, valid range: 0 to 59, error code: 5
double second; // Observer local second, valid range: 0 to <60, error code: 6
double delta_ut1; // Fractional second difference between UTC and UT which is used
// to adjust UTC for earth's irregular rotation rate and is derived
// from observation only and is reported in this bulletin:
// http://maia.usno.navy.mil/ser7/ser7.dat,
// where delta_ut1 = DUT1
// valid range: -1 to 1 second (exclusive), error code 17
double delta_t; // Difference between earth rotation time and terrestrial time
// It is derived from observation only and is reported in this
// bulletin: http://maia.usno.navy.mil/ser7/ser7.dat,
// where delta_t = 32.184 + (TAI-UTC) - DUT1
// valid range: -8000 to 8000 seconds, error code: 7
double timezone; // Observer time zone (negative west of Greenwich)
// valid range: -18 to 18 hours, error code: 8
double longitude; // Observer longitude (negative west of Greenwich)
// valid range: -180 to 180 degrees, error code: 9
double latitude; // Observer latitude (negative south of equator)
// valid range: -90 to 90 degrees, error code: 10
double elevation; // Observer elevation [meters]
// valid range: -6500000 or higher meters, error code: 11
double pressure; // Annual average local pressure [millibars]
// valid range: 0 to 5000 millibars, error code: 12
double temperature; // Annual average local temperature [degrees Celsius]
// valid range: -273 to 6000 degrees Celsius, error code; 13
double slope; // Surface slope (measured from the horizontal plane)
// valid range: -360 to 360 degrees, error code: 14
double azm_rotation; // Surface azimuth rotation (measured from south to projection of
// surface normal on horizontal plane, negative east)
// valid range: -360 to 360 degrees, error code: 15
double atmos_refract;// Atmospheric refraction at sunrise and sunset (0.5667 deg is typical)
// valid range: -5 to 5 degrees, error code: 16
int function; // Switch to choose functions for desired output (from enumeration)
//-----------------Intermediate OUTPUT VALUES--------------------
double jd; //Julian day
double jc; //Julian century
double jde; //Julian ephemeris day
double jce; //Julian ephemeris century
double jme; //Julian ephemeris millennium
double l; //earth heliocentric longitude [degrees]
double b; //earth heliocentric latitude [degrees]
double r; //earth radius vector [Astronomical Units, AU]
double theta; //geocentric longitude [degrees]
double beta; //geocentric latitude [degrees]
double x0; //mean elongation (moon-sun) [degrees]
double x1; //mean anomaly (sun) [degrees]
double x2; //mean anomaly (moon) [degrees]
double x3; //argument latitude (moon) [degrees]
double x4; //ascending longitude (moon) [degrees]
double del_psi; //nutation longitude [degrees]
double del_epsilon; //nutation obliquity [degrees]
double epsilon0; //ecliptic mean obliquity [arc seconds]
double epsilon; //ecliptic true obliquity [degrees]
double del_tau; //aberration correction [degrees]
double lamda; //apparent sun longitude [degrees]
double nu0; //Greenwich mean sidereal time [degrees]
double nu; //Greenwich sidereal time [degrees]
double alpha; //geocentric sun right ascension [degrees]
double delta; //geocentric sun declination [degrees]
double h; //observer hour angle [degrees]
double xi; //sun equatorial horizontal parallax [degrees]
double del_alpha; //sun right ascension parallax [degrees]
double delta_prime; //topocentric sun declination [degrees]
double alpha_prime; //topocentric sun right ascension [degrees]
double h_prime; //topocentric local hour angle [degrees]
double e0; //topocentric elevation angle (uncorrected) [degrees]
double del_e; //atmospheric refraction correction [degrees]
double e; //topocentric elevation angle (corrected) [degrees]
double eot; //equation of time [minutes]
double srha; //sunrise hour angle [degrees]
double ssha; //sunset hour angle [degrees]
double sta; //sun transit altitude [degrees]
//---------------------Final OUTPUT VALUES------------------------
double zenith; //topocentric zenith angle [degrees]
double azimuth_astro;//topocentric azimuth angle (westward from south) [for astronomers]
double azimuth; //topocentric azimuth angle (eastward from north) [for navigators and solar radiation]
double incidence; //surface incidence angle [degrees]
double suntransit; //local sun transit time (or solar noon) [fractional hour]
double sunrise; //local sunrise time (+/- 30 seconds) [fractional hour]
double sunset; //local sunset time (+/- 30 seconds) [fractional hour]
} spa_data;
//-------------- Utility functions for other applications (such as NREL's SAMPA) --------------
double deg2rad(double degrees);
double rad2deg(double radians);
double limit_degrees(double degrees);
double third_order_polynomial(double a, double b, double c, double d, double x);
double geocentric_right_ascension(double lamda, double epsilon, double beta);
double geocentric_declination(double beta, double epsilon, double lamda);
double observer_hour_angle(double nu, double longitude, double alpha_deg);
void right_ascension_parallax_and_topocentric_dec(double latitude, double elevation,
double xi, double h, double delta, double *delta_alpha, double *delta_prime);
double topocentric_right_ascension(double alpha_deg, double delta_alpha);
double topocentric_local_hour_angle(double h, double delta_alpha);
double topocentric_elevation_angle(double latitude, double delta_prime, double h_prime);
double atmospheric_refraction_correction(double pressure, double temperature,
double atmos_refract, double e0);
double topocentric_elevation_angle_corrected(double e0, double delta_e);
double topocentric_zenith_angle(double e);
double topocentric_azimuth_angle_astro(double h_prime, double latitude, double delta_prime);
double topocentric_azimuth_angle(double azimuth_astro);
//Calculate SPA output values (in structure) based on input values passed in structure
int spa_calculate(spa_data *spa);
#endif

77
src/spa_wrapper.c Normal file
View file

@ -0,0 +1,77 @@
// src/spa_wrapper.c
#include "spa.h"
#include <stdlib.h> // For malloc and free
typedef struct {
double zenith;
double azimuth;
double incidence;
double sunrise;
double sunset;
double solar_noon;
double sun_transit_alt;
} spa_result;
spa_result* spa_calculate_wrapper(
int year, int month, int day,
int hour, int minute, double second,
double timezone,
double latitude, double longitude, double elevation,
double pressure, double temperature,
double slope, double azm_rotation, double atmos_refract)
{
// Allocate memory for the result
spa_result* result = (spa_result*)malloc(sizeof(spa_result));
if (!result) return NULL;
spa_data spa;
spa.year = year;
spa.month = month;
spa.day = day;
spa.hour = hour;
spa.minute = minute;
spa.second = second;
spa.timezone = timezone;
spa.latitude = latitude;
spa.longitude = longitude;
spa.elevation = elevation;
// Set default values for optional inputs if they are not given
spa.pressure = (pressure == 0.0) ? 820.0 : pressure; // Standard atmospheric pressure
spa.temperature = (temperature == 0.0) ? 11.0 : temperature; // Standard temperature
spa.slope = slope; // Surface slope angle (default 0.0)
spa.azm_rotation = azm_rotation; // Surface azimuth angle (default 0.0)
spa.atmos_refract = (atmos_refract == 0.0) ? 0.5667 : atmos_refract;
// Set the calculation mode to SPA_ALL
spa.function = SPA_ALL;
// Calculate SPA values
int result_code = spa_calculate(&spa);
if (result_code == 0) {
result->zenith = spa.zenith;
result->azimuth = spa.azimuth;
result->incidence = spa.incidence;
result->sunrise = spa.sunrise;
result->sunset = spa.sunset;
result->solar_noon = spa.suntransit;
result->sun_transit_alt = spa.sta;
} else {
// Error handling (fill with zeros or other default values)
result->zenith = 0;
result->azimuth = 0;
result->incidence = 0;
result->sunrise = 0;
result->sunset = 0;
result->solar_noon = 0;
result->sun_transit_alt = 0;
}
return result;
}
// Function to free the allocated result memory
void spa_free_result(spa_result* result) {
free(result);
}

36
test.js Normal file
View file

@ -0,0 +1,36 @@
// test.js
// Import the 'solar-spa' module
const spa = require('./solar-spa.js');
// Define input parameters for a specific date, time, and location
const date = new Date(2023, 3, 1, 0, 0, 0); // April 1, 2023 at Midnight
const latitude = 40.7128; // Latitude of New York City, USA
const longitude = -74.0060; // Longitude of New York City, USA
const elevation = 10; // Elevation in meters (approximately)
const pressure = 1013.25; // Optional input: Atmospheric pressure (millibars)
const temperature = 20; // Optional input: Temperature (degrees Celsius)
const refraction = 0.5667; // Optional input: Atmospheric refraction (degrees)
// Convert fractional hours to formatted time string for sunrise, sunset, solar_noon
function formatTime(hours) {
const milliseconds = hours * 60 * 60 * 1000;
const date = new Date(milliseconds);
return date.toISOString().substr(11, 12);
}
// Call the 'spa' function and log the results
spa(date, latitude, longitude, elevation, temperature, pressure, refraction)
.then(result => {
console.log({
zenith: result.zenith,
azimuth: result.azimuth,
incidence: result.incidence,
sun_transit_alt: result.sun_transit_alt,
sunrise: result.sunrise,
solar_noon: result.solar_noon,
sunset: result.sunset
});
})
.catch(error => {
console.error(error);
});