mirror of
https://github.com/acamarata/solar-spa.git
synced 2026-06-30 19:04:28 +00:00
first commit
This commit is contained in:
commit
f10c64c5f1
10 changed files with 3427 additions and 0 deletions
2
.npmignore
Normal file
2
.npmignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
src/
|
||||
test.js
|
||||
71
README.md
Normal file
71
README.md
Normal 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
11
package.json
Normal 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
61
solar-spa.js
Normal 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);
|
||||
};
|
||||
});
|
||||
};
|
||||
BIN
spa.wasm
Executable file
BIN
spa.wasm
Executable file
Binary file not shown.
206
src/spa.h
Normal file
206
src/spa.h
Normal 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
77
src/spa_wrapper.c
Normal 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
36
test.js
Normal 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);
|
||||
});
|
||||
Loading…
Reference in a new issue