commit f10c64c5f1a92d73a804eddb03c1ffe91ff8f6b4 Author: Ali Camarata Date: Fri Mar 31 23:26:40 2023 -0400 first commit diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..00fc139 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +src/ +test.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..1afb3b1 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3119f52 --- /dev/null +++ b/package.json @@ -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" +} diff --git a/solar-spa.js b/solar-spa.js new file mode 100644 index 0000000..5fb72f8 --- /dev/null +++ b/solar-spa.js @@ -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); + }; + }); +}; diff --git a/spa.js b/spa.js new file mode 100644 index 0000000..82db4dd --- /dev/null +++ b/spa.js @@ -0,0 +1,1790 @@ +// include: shell.js +// The Module object: Our interface to the outside world. We import +// and export values on it. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(Module) { ..generated code.. } +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to check if Module already exists (e.g. case 3 above). +// Substitution will be replaced with actual code on later stage of the build, +// this way Closure Compiler will not mangle it (e.g. case 4. above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. +var Module = typeof Module != 'undefined' ? Module : {}; + +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) + + +// Sometimes an existing Module object exists with properties +// meant to overwrite the default module functionality. Here +// we collect those properties and reapply _after_ we configure +// the current environment's defaults to avoid having to be so +// defensive during initialization. +var moduleOverrides = Object.assign({}, Module); + +var arguments_ = []; +var thisProgram = './this.program'; +var quit_ = (status, toThrow) => { + throw toThrow; +}; + +// Determine the runtime environment we are in. You can customize this by +// setting the ENVIRONMENT setting at compile time (see settings.js). + +// Attempt to auto-detect the environment +var ENVIRONMENT_IS_WEB = typeof window == 'object'; +var ENVIRONMENT_IS_WORKER = typeof importScripts == 'function'; +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string'; +var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +if (Module['ENVIRONMENT']) { + throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); +} + +// `/` should be present at the end if `scriptDirectory` is not empty +var scriptDirectory = ''; +function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; +} + +// Hooks that are implemented differently in different runtime environments. +var read_, + readAsync, + readBinary, + setWindowTitle; + +if (ENVIRONMENT_IS_NODE) { + if (typeof process == 'undefined' || !process.release || process.release.name !== 'node') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + var nodeVersion = process.versions.node; + var numericVersion = nodeVersion.split('.').slice(0, 3); + numericVersion = (numericVersion[0] * 10000) + (numericVersion[1] * 100) + numericVersion[2] * 1; + var minVersion = 101900; + if (numericVersion < 101900) { + throw new Error('This emscripten-generated code requires node v10.19.19.0 (detected v' + nodeVersion + ')'); + } + + // `require()` is no-op in an ESM module, use `createRequire()` to construct + // the require()` function. This is only necessary for multi-environment + // builds, `-sENVIRONMENT=node` emits a static import declaration instead. + // TODO: Swap all `require()`'s with `import()`'s? + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require('fs'); + var nodePath = require('path'); + + if (ENVIRONMENT_IS_WORKER) { + scriptDirectory = nodePath.dirname(scriptDirectory) + '/'; + } else { + scriptDirectory = __dirname + '/'; + } + +// include: node_shell_read.js +read_ = (filename, binary) => { + // We need to re-wrap `file://` strings to URLs. Normalizing isn't + // necessary in that case, the path should already be absolute. + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); + return fs.readFileSync(filename, binary ? undefined : 'utf8'); +}; + +readBinary = (filename) => { + var ret = read_(filename, true); + if (!ret.buffer) { + ret = new Uint8Array(ret); + } + assert(ret.buffer); + return ret; +}; + +readAsync = (filename, onload, onerror) => { + // See the comment in the `read_` function. + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); + fs.readFile(filename, function(err, data) { + if (err) onerror(err); + else onload(data.buffer); + }); +}; + +// end include: node_shell_read.js + if (process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, '/'); + } + + arguments_ = process.argv.slice(2); + + if (typeof module != 'undefined') { + module['exports'] = Module; + } + + process.on('uncaughtException', function(ex) { + // suppress ExitStatus exceptions from showing an error + if (ex !== 'unwind' && !(ex instanceof ExitStatus) && !(ex.context instanceof ExitStatus)) { + throw ex; + } + }); + + // Without this older versions of node (< v15) will log unhandled rejections + // but return 0, which is not normally the desired behaviour. This is + // not be needed with node v15 and about because it is now the default + // behaviour: + // See https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode + var nodeMajor = process.versions.node.split(".")[0]; + if (nodeMajor < 15) { + process.on('unhandledRejection', function(reason) { throw reason; }); + } + + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + + Module['inspect'] = function () { return '[Emscripten Module object]'; }; + +} else +if (ENVIRONMENT_IS_SHELL) { + + if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof importScripts == 'function') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + if (typeof read != 'undefined') { + read_ = function shell_read(f) { + return read(f); + }; + } + + readBinary = function readBinary(f) { + let data; + if (typeof readbuffer == 'function') { + return new Uint8Array(readbuffer(f)); + } + data = read(f, 'binary'); + assert(typeof data == 'object'); + return data; + }; + + readAsync = function readAsync(f, onload, onerror) { + setTimeout(() => onload(readBinary(f)), 0); + }; + + if (typeof clearTimeout == 'undefined') { + globalThis.clearTimeout = (id) => {}; + } + + if (typeof scriptArgs != 'undefined') { + arguments_ = scriptArgs; + } else if (typeof arguments != 'undefined') { + arguments_ = arguments; + } + + if (typeof quit == 'function') { + quit_ = (status, toThrow) => { + // Unlike node which has process.exitCode, d8 has no such mechanism. So we + // have no way to set the exit code and then let the program exit with + // that code when it naturally stops running (say, when all setTimeouts + // have completed). For that reason, we must call `quit` - the only way to + // set the exit code - but quit also halts immediately. To increase + // consistency with node (and the web) we schedule the actual quit call + // using a setTimeout to give the current stack and any exception handlers + // a chance to run. This enables features such as addOnPostRun (which + // expected to be able to run code after main returns). + setTimeout(() => { + if (!(toThrow instanceof ExitStatus)) { + let toLog = toThrow; + if (toThrow && typeof toThrow == 'object' && toThrow.stack) { + toLog = [toThrow, toThrow.stack]; + } + err('exiting due to exception: ' + toLog); + } + quit(status); + }); + throw toThrow; + }; + } + + if (typeof print != 'undefined') { + // Prefer to use print/printErr where they exist, as they usually work better. + if (typeof console == 'undefined') console = /** @type{!Console} */({}); + console.log = /** @type{!function(this:Console, ...*): undefined} */ (print); + console.warn = console.error = /** @type{!function(this:Console, ...*): undefined} */ (typeof printErr != 'undefined' ? printErr : print); + } + +} else + +// Note that this includes Node.js workers when relevant (pthreads is enabled). +// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and +// ENVIRONMENT_IS_NODE. +if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled + scriptDirectory = self.location.href; + } else if (typeof document != 'undefined' && document.currentScript) { // web + scriptDirectory = document.currentScript.src; + } + // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. + // otherwise, slice off the final part of the url to find the script directory. + // if scriptDirectory does not contain a slash, lastIndexOf will return -1, + // and scriptDirectory will correctly be replaced with an empty string. + // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), + // they are removed because they could contain a slash. + if (scriptDirectory.indexOf('blob:') !== 0) { + scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf('/')+1); + } else { + scriptDirectory = ''; + } + + if (!(typeof window == 'object' || typeof importScripts == 'function')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + // Differentiate the Web Worker from the Node Worker case, as reading must + // be done differently. + { +// include: web_or_worker_shell_read.js +read_ = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.send(null); + return xhr.responseText; + } + + if (ENVIRONMENT_IS_WORKER) { + readBinary = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.responseType = 'arraybuffer'; + xhr.send(null); + return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); + }; + } + + readAsync = (url, onload, onerror) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = () => { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + onload(xhr.response); + return; + } + onerror(); + }; + xhr.onerror = onerror; + xhr.send(null); + } + +// end include: web_or_worker_shell_read.js + } + + setWindowTitle = (title) => document.title = title; +} else +{ + throw new Error('environment detection error'); +} + +var out = Module['print'] || console.log.bind(console); +var err = Module['printErr'] || console.warn.bind(console); + +// Merge back in the overrides +Object.assign(Module, moduleOverrides); +// Free the object hierarchy contained in the overrides, this lets the GC +// reclaim data used e.g. in memoryInitializerRequest, which is a large typed array. +moduleOverrides = null; +checkIncomingModuleAPI(); + +// Emit code to handle expected values on the Module object. This applies Module.x +// to the proper local x. This has two benefits: first, we only emit it if it is +// expected to arrive, and second, by using a local everywhere else that can be +// minified. + +if (Module['arguments']) arguments_ = Module['arguments'];legacyModuleProp('arguments', 'arguments_'); + +if (Module['thisProgram']) thisProgram = Module['thisProgram'];legacyModuleProp('thisProgram', 'thisProgram'); + +if (Module['quit']) quit_ = Module['quit'];legacyModuleProp('quit', 'quit_'); + +// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message +// Assertions on removed incoming Module JS APIs. +assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['read'] == 'undefined', 'Module.read option was removed (modify read_ in JS)'); +assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); +assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); +assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify setWindowTitle in JS)'); +assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); +legacyModuleProp('read', 'read_'); +legacyModuleProp('readAsync', 'readAsync'); +legacyModuleProp('readBinary', 'readBinary'); +legacyModuleProp('setWindowTitle', 'setWindowTitle'); +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +assert(!ENVIRONMENT_IS_SHELL, "shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."); + + +// end include: shell.js +// include: preamble.js +// === Preamble library stuff === + +// Documentation for the public APIs defined in this file must be updated in: +// site/source/docs/api_reference/preamble.js.rst +// A prebuilt local version of the documentation is available at: +// site/build/text/docs/api_reference/preamble.js.txt +// You can also build docs locally as HTML or other formats in site/ +// An online HTML version (which may be of a different version of Emscripten) +// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + +var wasmBinary; +if (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];legacyModuleProp('wasmBinary', 'wasmBinary'); +var noExitRuntime = Module['noExitRuntime'] || true;legacyModuleProp('noExitRuntime', 'noExitRuntime'); + +if (typeof WebAssembly != 'object') { + abort('no native wasm support detected'); +} + +// Wasm globals + +var wasmMemory; + +//======================================== +// Runtime essentials +//======================================== + +// whether we are quitting the application. no code should run after this. +// set in exit() and abort() +var ABORT = false; + +// set by exit() and abort(). Passed to 'onExit' handler. +// NOTE: This is also used as the process return code code in shell environments +// but only when noExitRuntime is false. +var EXITSTATUS; + +/** @type {function(*, string=)} */ +function assert(condition, text) { + if (!condition) { + abort('Assertion failed' + (text ? ': ' + text : '')); + } +} + +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. +function _malloc() { + abort("malloc() called but not included in the build - add '_malloc' to EXPORTED_FUNCTIONS"); +} +function _free() { + // Show a helpful error since we used to include free by default in the past. + abort("free() called but not included in the build - add '_free' to EXPORTED_FUNCTIONS"); +} + +// include: runtime_strings.js +// runtime_strings.js: String related runtime functions that are part of both +// MINIMAL_RUNTIME and regular runtime. + +var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined; + +/** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number} idx + * @param {number=} maxBytesToRead + * @return {string} + */ +function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; +} + +/** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first \0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ +function UTF8ToString(ptr, maxBytesToRead) { + assert(typeof ptr == 'number'); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; +} + +/** + * Copies the given Javascript String object 'str' to the given byte array at + * address 'outIdx', encoded in UTF8 form and null-terminated. The copy will + * require at most str.length*4+1 bytes of space in the HEAP. Use the function + * lengthBytesUTF8 to compute the exact number of bytes (excluding null + * terminator) that this function will write. + * + * @param {string} str - The Javascript string to copy. + * @param {ArrayBufferView|Array} heap - The array to copy to. Each + * index in this array is assumed + * to be one 8-byte element. + * @param {number} outIdx - The starting offset in the array to begin the copying. + * @param {number} maxBytesToWrite - The maximum number of bytes this function + * can write to the array. This count should + * include the null terminator, i.e. if + * maxBytesToWrite=1, only the null terminator + * will be written and nothing else. + * maxBytesToWrite=0 does not write any bytes + * to the output, not even the null + * terminator. + * @return {number} The number of bytes written, EXCLUDING the null terminator. + */ +function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { + // Parameter maxBytesToWrite is not optional. Negative values, 0, null, + // undefined and false each don't write out any bytes. + if (!(maxBytesToWrite > 0)) + return 0; + + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description + // and https://www.ietf.org/rfc/rfc2279.txt + // and https://tools.ietf.org/html/rfc3629 + var u = str.charCodeAt(i); // possibly a lead surrogate + if (u >= 0xD800 && u <= 0xDFFF) { + var u1 = str.charCodeAt(++i); + u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF); + } + if (u <= 0x7F) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 0x7FF) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 0xC0 | (u >> 6); + heap[outIdx++] = 0x80 | (u & 63); + } else if (u <= 0xFFFF) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 0xE0 | (u >> 12); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + if (u > 0x10FFFF) warnOnce('Invalid Unicode code point ' + ptrToString(u) + ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).'); + heap[outIdx++] = 0xF0 | (u >> 18); + heap[outIdx++] = 0x80 | ((u >> 12) & 63); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; +} + +/** + * Copies the given Javascript String object 'str' to the emscripten HEAP at + * address 'outPtr', null-terminated and encoded in UTF8 form. The copy will + * require at most str.length*4+1 bytes of space in the HEAP. + * Use the function lengthBytesUTF8 to compute the exact number of bytes + * (excluding null terminator) that this function will write. + * + * @return {number} The number of bytes written, EXCLUDING the null terminator. + */ +function stringToUTF8(str, outPtr, maxBytesToWrite) { + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); + return stringToUTF8Array(str, HEAPU8,outPtr, maxBytesToWrite); +} + +/** + * Returns the number of bytes the given Javascript string takes if encoded as a + * UTF8 byte array, EXCLUDING the null terminator byte. + * + * @param {string} str - JavaScript string to operator on + * @return {number} Length, in bytes, of the UTF8 encoded string. + */ +function lengthBytesUTF8(str) { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); // possibly a lead surrogate + if (c <= 0x7F) { + len++; + } else if (c <= 0x7FF) { + len += 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + len += 4; ++i; + } else { + len += 3; + } + } + return len; +} + +// end include: runtime_strings.js +// Memory management + +var HEAP, +/** @type {!Int8Array} */ + HEAP8, +/** @type {!Uint8Array} */ + HEAPU8, +/** @type {!Int16Array} */ + HEAP16, +/** @type {!Uint16Array} */ + HEAPU16, +/** @type {!Int32Array} */ + HEAP32, +/** @type {!Uint32Array} */ + HEAPU32, +/** @type {!Float32Array} */ + HEAPF32, +/** @type {!Float64Array} */ + HEAPF64; + +function updateMemoryViews() { + var b = wasmMemory.buffer; + Module['HEAP8'] = HEAP8 = new Int8Array(b); + Module['HEAP16'] = HEAP16 = new Int16Array(b); + Module['HEAP32'] = HEAP32 = new Int32Array(b); + Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); + Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); + Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); + Module['HEAPF32'] = HEAPF32 = new Float32Array(b); + Module['HEAPF64'] = HEAPF64 = new Float64Array(b); +} + +assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + +assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, + 'JS engine does not provide full typed array support'); + +// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY +assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); +assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + +// include: runtime_init_table.js +// In regular non-RELOCATABLE mode the table is exported +// from the wasm module and this will be assigned once +// the exports are available. +var wasmTable; + +// end include: runtime_init_table.js +// include: runtime_stack_check.js +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with the (separate) address-zero check + // below. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = 0x02135467; + HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[0] = 0x63736d65; /* 'emsc' */ +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)>>2)]; + var cookie2 = HEAPU32[(((max)+(4))>>2)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort('Stack overflow! Stack cookie has been overwritten at ' + ptrToString(max) + ', expected hex dwords 0x89BACDFE and 0x2135467, but received ' + ptrToString(cookie2) + ' ' + ptrToString(cookie1)); + } + // Also test the global address 0 for integrity. + if (HEAPU32[0] !== 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} + +// end include: runtime_stack_check.js +// include: runtime_assertions.js +// Endianness check +(function() { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; +})(); + +// end include: runtime_assertions.js +var __ATPRERUN__ = []; // functions called before the runtime is initialized +var __ATINIT__ = []; // functions called during startup +var __ATEXIT__ = []; // functions called during shutdown +var __ATPOSTRUN__ = []; // functions called after the main() is called + +var runtimeInitialized = false; + +var runtimeKeepaliveCounter = 0; + +function keepRuntimeAlive() { + return noExitRuntime || runtimeKeepaliveCounter > 0; +} + +function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); +} + +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; + + checkStackCookie(); + + + callRuntimeCallbacks(__ATINIT__); +} + +function postRun() { + checkStackCookie(); + + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + + callRuntimeCallbacks(__ATPOSTRUN__); +} + +function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); +} + +function addOnInit(cb) { + __ATINIT__.unshift(cb); +} + +function addOnExit(cb) { +} + +function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); +} + +// include: runtime_math.js +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc + +assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); + +// end include: runtime_math.js +// A counter of dependencies for calling run(). If we need to +// do asynchronous work before running, increment this and +// decrement it. Incrementing must happen in a place like +// Module.preRun (used by emcc to add file preloading). +// Note that you can add dependencies in preRun, even though +// it happens right before run - run will be postponed until +// the dependencies are met. +var runDependencies = 0; +var runDependencyWatcher = null; +var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled +var runDependencyTracking = {}; + +function getUniqueRunDependency(id) { + var orig = id; + while (1) { + if (!runDependencyTracking[id]) return id; + id = orig + Math.random(); + } +} + +function addRunDependency(id) { + runDependencies++; + + if (Module['monitorRunDependencies']) { + Module['monitorRunDependencies'](runDependencies); + } + + if (id) { + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && typeof setInterval != 'undefined') { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(function() { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err('dependency: ' + dep); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + } + } else { + err('warning: run dependency added without ID'); + } +} + +function removeRunDependency(id) { + runDependencies--; + + if (Module['monitorRunDependencies']) { + Module['monitorRunDependencies'](runDependencies); + } + + if (id) { + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + } else { + err('warning: run dependency removed without ID'); + } + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } +} + +/** @param {string|number=} what */ +function abort(what) { + if (Module['onAbort']) { + Module['onAbort'](what); + } + + what = 'Aborted(' + what + ')'; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + + ABORT = true; + EXITSTATUS = 1; + + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + + // Suppress closure compiler warning here. Closure compiler's builtin extern + // defintion for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ + var e = new WebAssembly.RuntimeError(what); + + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; +} + +// include: memoryprofiler.js +// end include: memoryprofiler.js +// show errors on likely calls to FS when it was not included +var FS = { + error: function() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); + }, + init: function() { FS.error() }, + createDataFile: function() { FS.error() }, + createPreloadedFile: function() { FS.error() }, + createLazyFile: function() { FS.error() }, + open: function() { FS.error() }, + mkdev: function() { FS.error() }, + registerDevice: function() { FS.error() }, + analyzePath: function() { FS.error() }, + loadFilesFromDB: function() { FS.error() }, + + ErrnoError: function ErrnoError() { FS.error() }, +}; +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; + +// include: URIUtils.js +// Prefix of data URIs emitted by SINGLE_FILE and related options. +var dataURIPrefix = 'data:application/octet-stream;base64,'; + +// Indicates whether filename is a base64 data URI. +function isDataURI(filename) { + // Prefix of data URIs emitted by SINGLE_FILE and related options. + return filename.startsWith(dataURIPrefix); +} + +// Indicates whether filename is delivered via file protocol (as opposed to http/https) +function isFileURI(filename) { + return filename.startsWith('file://'); +} + +// end include: URIUtils.js +/** @param {boolean=} fixedasm */ +function createExportWrapper(name, fixedasm) { + return function() { + var displayName = name; + var asm = fixedasm; + if (!fixedasm) { + asm = Module['asm']; + } + assert(runtimeInitialized, 'native function `' + displayName + '` called before runtime initialization'); + if (!asm[name]) { + assert(asm[name], 'exported native function `' + displayName + '` not found'); + } + return asm[name].apply(null, arguments); + }; +} + +// include: runtime_exceptions.js +// end include: runtime_exceptions.js +var wasmBinaryFile; + wasmBinaryFile = 'spa.wasm'; + if (!isDataURI(wasmBinaryFile)) { + wasmBinaryFile = locateFile(wasmBinaryFile); + } + +function getBinary(file) { + try { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + throw "both async and sync fetching of the wasm failed"; + } + catch (err) { + abort(err); + } +} + +function getBinaryPromise(binaryFile) { + // If we don't have the binary yet, try to to load it asynchronously. + // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. + // See https://github.com/github/fetch/pull/92#issuecomment-140665932 + // Cordova or Electron apps are typically loaded from a file:// url. + // So use fetch if it is available and the url is not a file, otherwise fall back to XHR. + if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) { + if (typeof fetch == 'function' + && !isFileURI(binaryFile) + ) { + return fetch(binaryFile, { credentials: 'same-origin' }).then(function(response) { + if (!response['ok']) { + throw "failed to load wasm binary file at '" + binaryFile + "'"; + } + return response['arrayBuffer'](); + }).catch(function () { + return getBinary(binaryFile); + }); + } + else { + if (readAsync) { + // fetch is not available or url is file => try XHR (readAsync uses XHR internally) + return new Promise(function(resolve, reject) { + readAsync(binaryFile, function(response) { resolve(new Uint8Array(/** @type{!ArrayBuffer} */(response))) }, reject) + }); + } + } + } + + // Otherwise, getBinary should be able to get it synchronously + return Promise.resolve().then(function() { return getBinary(binaryFile); }); +} + +function instantiateArrayBuffer(binaryFile, imports, receiver) { + return getBinaryPromise(binaryFile).then(function(binary) { + return WebAssembly.instantiate(binary, imports); + }).then(function (instance) { + return instance; + }).then(receiver, function(reason) { + err('failed to asynchronously prepare wasm: ' + reason); + + // Warn on some common problems. + if (isFileURI(wasmBinaryFile)) { + err('warning: Loading from a file URI (' + wasmBinaryFile + ') is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing'); + } + abort(reason); + }); +} + +function instantiateAsync(binary, binaryFile, imports, callback) { + if (!binary && + typeof WebAssembly.instantiateStreaming == 'function' && + !isDataURI(binaryFile) && + // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. + !isFileURI(binaryFile) && + // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 + !ENVIRONMENT_IS_NODE && + typeof fetch == 'function') { + return fetch(binaryFile, { credentials: 'same-origin' }).then(function(response) { + // Suppress closure warning here since the upstream definition for + // instantiateStreaming only allows Promise rather than + // an actual Response. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. + /** @suppress {checkTypes} */ + var result = WebAssembly.instantiateStreaming(response, imports); + + return result.then( + callback, + function(reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err('wasm streaming compile failed: ' + reason); + err('falling back to ArrayBuffer instantiation'); + return instantiateArrayBuffer(binaryFile, imports, callback); + }); + }); + } else { + return instantiateArrayBuffer(binaryFile, imports, callback); + } +} + +// Create the wasm instance. +// Receives the wasm imports, returns the exports. +function createWasm() { + // prepare imports + var info = { + 'env': wasmImports, + 'wasi_snapshot_preview1': wasmImports, + }; + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + /** @param {WebAssembly.Module=} module*/ + function receiveInstance(instance, module) { + var exports = instance.exports; + + Module['asm'] = exports; + + wasmMemory = Module['asm']['memory']; + assert(wasmMemory, "memory not found in wasm exports"); + // This assertion doesn't hold when emscripten is run in --post-link + // mode. + // TODO(sbc): Read INITIAL_MEMORY out of the wasm file in post-link mode. + //assert(wasmMemory.buffer.byteLength === 16777216); + updateMemoryViews(); + + wasmTable = Module['asm']['__indirect_function_table']; + assert(wasmTable, "table not found in wasm exports"); + + addOnInit(Module['asm']['__wasm_call_ctors']); + + removeRunDependency('wasm-instantiate'); + + return exports; + } + // wait for the pthread pool (if any) + addRunDependency('wasm-instantiate'); + + // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; + // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. + // When the regression is fixed, can restore the above PTHREADS-enabled path. + receiveInstance(result['instance']); + } + + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to run the instantiation parallel + // to any other async startup actions they are performing. + // Also pthreads and wasm workers initialize the wasm instance through this path. + if (Module['instantiateWasm']) { + try { + return Module['instantiateWasm'](info, receiveInstance); + } catch(e) { + err('Module.instantiateWasm callback failed with error: ' + e); + return false; + } + } + + instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult); + return {}; // no exports yet; we'll fill them in later +} + +// Globals used by JS i64 conversions (see makeSetValue) +var tempDouble; +var tempI64; + +// include: runtime_debug.js +function legacyModuleProp(prop, newName) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + get: function() { + abort('Module.' + prop + ' has been replaced with plain ' + newName + ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)'); + } + }); + } +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort('`Module.' + prop + '` was supplied but `' + prop + '` not included in INCOMING_MODULE_JS_API'); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +function missingGlobal(sym, msg) { + if (typeof globalThis !== 'undefined') { + Object.defineProperty(globalThis, sym, { + configurable: true, + get: function() { + warnOnce('`' + sym + '` is not longer defined by emscripten. ' + msg); + return undefined; + } + }); + } +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); + +function missingLibrarySymbol(sym) { + if (typeof globalThis !== 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get: function() { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = '`' + sym + '` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line'; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += " (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=" + librarySymbol + ")"; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + return undefined; + } + }); + } + // Any symbol that is not included from the JS libary is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get: function() { + var msg = "'" + sym + "' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)"; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + } + }); + } +} + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(text) { + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as errors. + console.error(text); +} + +// end include: runtime_debug.js +// === Body === + + +// end include: preamble.js + + /** @constructor */ + function ExitStatus(status) { + this.name = 'ExitStatus'; + this.message = 'Program terminated with exit(' + status + ')'; + this.status = status; + } + + function callRuntimeCallbacks(callbacks) { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + } + + + /** + * @param {number} ptr + * @param {string} type + */ + function getValue(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': return HEAP8[((ptr)>>0)]; + case 'i8': return HEAP8[((ptr)>>0)]; + case 'i16': return HEAP16[((ptr)>>1)]; + case 'i32': return HEAP32[((ptr)>>2)]; + case 'i64': return HEAP32[((ptr)>>2)]; + case 'float': return HEAPF32[((ptr)>>2)]; + case 'double': return HEAPF64[((ptr)>>3)]; + case '*': return HEAPU32[((ptr)>>2)]; + default: abort('invalid type for getValue: ' + type); + } + } + + function ptrToString(ptr) { + assert(typeof ptr === 'number'); + return '0x' + ptr.toString(16).padStart(8, '0'); + } + + + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': HEAP8[((ptr)>>0)] = value; break; + case 'i8': HEAP8[((ptr)>>0)] = value; break; + case 'i16': HEAP16[((ptr)>>1)] = value; break; + case 'i32': HEAP32[((ptr)>>2)] = value; break; + case 'i64': (tempI64 = [value>>>0,(tempDouble=value,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((ptr)>>2)] = tempI64[0],HEAP32[(((ptr)+(4))>>2)] = tempI64[1]); break; + case 'float': HEAPF32[((ptr)>>2)] = value; break; + case 'double': HEAPF64[((ptr)>>3)] = value; break; + case '*': HEAPU32[((ptr)>>2)] = value; break; + default: abort('invalid type for setValue: ' + type); + } + } + + function warnOnce(text) { + if (!warnOnce.shown) warnOnce.shown = {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + } + + function _emscripten_memcpy_big(dest, src, num) { + HEAPU8.copyWithin(dest, src, src + num); + } + + function getHeapMax() { + return HEAPU8.length; + } + + function abortOnCannotGrowMemory(requestedSize) { + abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ' + HEAP8.length + ', (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0'); + } + function _emscripten_resize_heap(requestedSize) { + var oldSize = HEAPU8.length; + requestedSize = requestedSize >>> 0; + abortOnCannotGrowMemory(requestedSize); + } + + function getCFunc(ident) { + var func = Module['_' + ident]; // closure exported function + assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported'); + return func; + } + + function writeArrayToMemory(array, buffer) { + assert(array.length >= 0, 'writeArrayToMemory array must have a length (should be an array or typed array)') + HEAP8.set(array, buffer); + } + + /** + * @param {string|null=} returnType + * @param {Array=} argTypes + * @param {Arguments|Array=} args + * @param {Object=} opts + */ + function ccall(ident, returnType, argTypes, args, opts) { + // For fast lookup of conversion functions + var toC = { + 'string': (str) => { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { // null string + // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0' + var len = (str.length << 2) + 1; + ret = stackAlloc(len); + stringToUTF8(str, ret, len); + } + return ret; + }, + 'array': (arr) => { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + } + }; + + function convertReturnValue(ret) { + if (returnType === 'string') { + + return UTF8ToString(ret); + } + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + assert(returnType !== 'array', 'Return type should not be "array".'); + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + var ret = func.apply(null, cArgs); + function onDone(ret) { + if (stack !== 0) stackRestore(stack); + return convertReturnValue(ret); + } + + ret = onDone(ret); + return ret; + } + + + + /** + * @param {string=} returnType + * @param {Array=} argTypes + * @param {Object=} opts + */ + function cwrap(ident, returnType, argTypes, opts) { + return function() { + return ccall(ident, returnType, argTypes, arguments, opts); + } + } + +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); +} +var wasmImports = { + "emscripten_memcpy_big": _emscripten_memcpy_big, + "emscripten_resize_heap": _emscripten_resize_heap +}; +var asm = createWasm(); +/** @type {function(...*):?} */ +var ___wasm_call_ctors = createExportWrapper("__wasm_call_ctors"); +/** @type {function(...*):?} */ +var _spa_calculate_wrapper = Module["_spa_calculate_wrapper"] = createExportWrapper("spa_calculate_wrapper"); +/** @type {function(...*):?} */ +var _spa_free_result = Module["_spa_free_result"] = createExportWrapper("spa_free_result"); +/** @type {function(...*):?} */ +var ___errno_location = createExportWrapper("__errno_location"); +/** @type {function(...*):?} */ +var _fflush = Module["_fflush"] = createExportWrapper("fflush"); +/** @type {function(...*):?} */ +var _emscripten_stack_init = function() { + return (_emscripten_stack_init = Module["asm"]["emscripten_stack_init"]).apply(null, arguments); +}; + +/** @type {function(...*):?} */ +var _emscripten_stack_get_free = function() { + return (_emscripten_stack_get_free = Module["asm"]["emscripten_stack_get_free"]).apply(null, arguments); +}; + +/** @type {function(...*):?} */ +var _emscripten_stack_get_base = function() { + return (_emscripten_stack_get_base = Module["asm"]["emscripten_stack_get_base"]).apply(null, arguments); +}; + +/** @type {function(...*):?} */ +var _emscripten_stack_get_end = function() { + return (_emscripten_stack_get_end = Module["asm"]["emscripten_stack_get_end"]).apply(null, arguments); +}; + +/** @type {function(...*):?} */ +var stackSave = createExportWrapper("stackSave"); +/** @type {function(...*):?} */ +var stackRestore = createExportWrapper("stackRestore"); +/** @type {function(...*):?} */ +var stackAlloc = createExportWrapper("stackAlloc"); +/** @type {function(...*):?} */ +var _emscripten_stack_get_current = function() { + return (_emscripten_stack_get_current = Module["asm"]["emscripten_stack_get_current"]).apply(null, arguments); +}; + + + +// include: postamble.js +// === Auto-generated postamble setup entry stuff === + +Module["ccall"] = ccall; +Module["cwrap"] = cwrap; +Module["getValue"] = getValue; +var missingLibrarySymbols = [ + 'zeroMemory', + 'stringToNewUTF8', + 'exitJS', + 'emscripten_realloc_buffer', + 'setErrNo', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'getHostByName', + 'getRandomDevice', + 'traverseStack', + 'convertPCtoSourceLocation', + 'readEmAsmArgs', + 'jstoi_q', + 'jstoi_s', + 'getExecutableName', + 'listenOnce', + 'autoResumeAudioContext', + 'dynCallLegacy', + 'getDynCaller', + 'dynCall', + 'handleException', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'callUserCallback', + 'maybeExit', + 'safeSetTimeout', + 'asmjsMangle', + 'asyncLoad', + 'alignMemory', + 'mmapAlloc', + 'HandleAllocator', + 'getNativeTypeSize', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'uleb128Encode', + 'sigToWasmTypes', + 'generateFuncType', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'reallyNegative', + 'unSign', + 'strLen', + 'reSign', + 'formatString', + 'intArrayFromString', + 'intArrayToString', + 'AsciiToString', + 'stringToAscii', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'getSocketFromFD', + 'getSocketAddress', + 'registerKeyEventCallback', + 'maybeCStringToJsString', + 'findEventTarget', + 'findCanvasEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'battery', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'demangle', + 'demangleAll', + 'jsStackTrace', + 'stackTrace', + 'getEnvStrings', + 'checkWasiClock', + 'flush_NO_FILESYSTEM', + 'createDyncallWrapper', + 'setImmediateWrapped', + 'clearImmediateWrapped', + 'polyfillSetImmediate', + 'getPromise', + 'makePromise', + 'makePromiseCallback', + 'ExceptionInfo', + 'exception_addRef', + 'exception_decRef', + 'setMainLoop', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'heapAccessShiftForWebGLHeap', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + 'writeGLArray', + 'SDL_unicode', + 'SDL_ttfContext', + 'SDL_audio', + 'GLFW_Window', + 'runAndAbortIfError', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + +var unexportedSymbols = [ + 'run', + 'UTF8ArrayToString', + 'UTF8ToString', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'addOnPreRun', + 'addOnInit', + 'addOnPreMain', + 'addOnExit', + 'addOnPostRun', + 'addRunDependency', + 'removeRunDependency', + 'FS_createFolder', + 'FS_createPath', + 'FS_createDataFile', + 'FS_createPreloadedFile', + 'FS_createLazyFile', + 'FS_createLink', + 'FS_createDevice', + 'FS_unlink', + 'out', + 'err', + 'callMain', + 'abort', + 'keepRuntimeAlive', + 'wasmMemory', + 'stackAlloc', + 'stackSave', + 'stackRestore', + 'getTempRet0', + 'setTempRet0', + 'writeStackCookie', + 'checkStackCookie', + 'ptrToString', + 'getHeapMax', + 'abortOnCannotGrowMemory', + 'ENV', + 'ERRNO_CODES', + 'ERRNO_MESSAGES', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'UNWIND_CACHE', + 'readEmAsmArgsArray', + 'getCFunc', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'PATH', + 'PATH_FS', + 'UTF16Decoder', + 'writeArrayToMemory', + 'SYSCALLS', + 'JSEvents', + 'specialHTMLTargets', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'ExitStatus', + 'dlopenMissingError', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'Browser', + 'wget', + 'FS', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'GL', + 'AL', + 'SDL', + 'SDL_gfx', + 'GLUT', + 'EGL', + 'GLFW', + 'GLEW', + 'IDBStore', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + + + +var calledRun; + +dependenciesFulfilled = function runCaller() { + // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) + if (!calledRun) run(); + if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled +}; + +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + +function run() { + + if (runDependencies > 0) { + return; + } + + stackCheckInit(); + + preRun(); + + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + return; + } + + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + if (calledRun) return; + calledRun = true; + Module['calledRun'] = true; + + if (ABORT) return; + + initRuntime(); + + if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized'](); + + assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'); + + postRun(); + } + + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(function() { + setTimeout(function() { + Module['setStatus'](''); + }, 1); + doRun(); + }, 1); + } else + { + doRun(); + } + checkStackCookie(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + _fflush(0); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.'); + warnOnce('(this may also be due to not including full filesystem support - try building with -sFORCE_FILESYSTEM)'); + } +} + +if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].pop()(); + } +} + +run(); + + +// end include: postamble.js diff --git a/spa.wasm b/spa.wasm new file mode 100755 index 0000000..8d8f224 Binary files /dev/null and b/spa.wasm differ diff --git a/src/spa.c b/src/spa.c new file mode 100644 index 0000000..39ab711 --- /dev/null +++ b/src/spa.c @@ -0,0 +1,1173 @@ + + +///////////////////////////////////////////// +// Solar Position Algorithm (SPA) // +// for // +// Solar Radiation Application // +// // +// May 12, 2003 // +// // +// Filename: SPA.C // +// // +// Afshin Michael Andreas // +// Afshin.Andreas@NREL.gov (303)384-6383 // +// // +// Metrology Laboratory // +// Solar Radiation Research Laboratory // +// National Renewable Energy Laboratory // +// 15013 Denver W Pkwy, Golden, CO 80401 // +///////////////////////////////////////////// + +///////////////////////////////////////////// +// See the SPA.H header file for usage // +// // +// This code is based on the NREL // +// technical report "Solar Position // +// Algorithm for Solar Radiation // +// Application" by I. Reda & A. Andreas // +///////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// NOTICE +// Copyright (C) 2008-2011 Alliance for Sustainable Energy, LLC, All Rights Reserved +// +//The Solar Position Algorithm ("Software") is code in development prepared by employees of the +//Alliance for Sustainable Energy, LLC, (hereinafter the "Contractor"), under Contract No. +//DE-AC36-08GO28308 ("Contract") with the U.S. Department of Energy (the "DOE"). The United +//States Government has been granted for itself and others acting on its behalf a paid-up, non- +//exclusive, irrevocable, worldwide license in the Software to reproduce, prepare derivative +//works, and perform publicly and display publicly. Beginning five (5) years after the date +//permission to assert copyright is obtained from the DOE, and subject to any subsequent five +//(5) year renewals, the United States Government is granted for itself and others acting on +//its behalf a paid-up, non-exclusive, irrevocable, worldwide license in the Software to +//reproduce, prepare derivative works, distribute copies to the public, perform publicly and +//display publicly, and to permit others to do so. If the Contractor ceases to make this +//computer software available, it may be obtained from DOE's Office of Scientific and Technical +//Information's Energy Science and Technology Software Center (ESTSC) at P.O. Box 1020, Oak +//Ridge, TN 37831-1020. THIS SOFTWARE IS PROVIDED BY THE CONTRACTOR "AS IS" AND ANY EXPRESS OR +//IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +//AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRACTOR OR THE +//U.S. GOVERNMENT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +//WHATSOEVER, INCLUDING BUT NOT LIMITED TO CLAIMS ASSOCIATED WITH THE LOSS OF DATA OR PROFITS, +//WHICH MAY RESULT FROM AN ACTION IN CONTRACT, NEGLIGENCE OR OTHER TORTIOUS CLAIM THAT ARISES +//OUT OF OR IN CONNECTION WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. +// +//The Software is being provided for internal, noncommercial purposes only and shall not be +//re-distributed. Please contact the NREL Commercialization and Technology Transfer Office +//for information concerning a commercial license to use the Software, visit: +//http://midcdmz.nrel.gov/spa/ for the contact information. +// +//As a condition of using the Software in an application, the developer of the application +//agrees to reference the use of the Software and make this Notice readily accessible to any +//end-user in a Help|About screen or equivalent manner. +// +/////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Revised 27-FEB-2004 Andreas +// Added bounds check on inputs and return value for spa_calculate(). +// Revised 10-MAY-2004 Andreas +// Changed temperature bound check minimum from -273.15 to -273 degrees C. +// Revised 17-JUN-2004 Andreas +// Corrected a problem that caused a bogus sunrise/set/transit on the equinox. +// Revised 18-JUN-2004 Andreas +// Added a "function" input variable that allows the selecting of desired outputs. +// Revised 21-JUN-2004 Andreas +// Added 3 new intermediate output values to SPA structure (srha, ssha, & sta). +// Revised 23-JUN-2004 Andreas +// Enumerations for "function" were renamed and 2 were added. +// Prevented bound checks on inputs that are not used (based on function). +// Revised 01-SEP-2004 Andreas +// Changed a local variable from integer to double. +// Revised 12-JUL-2005 Andreas +// Put a limit on the EOT calculation, so that the result is between -20 and 20. +// Revised 26-OCT-2005 Andreas +// Set the atmos. refraction correction to zero, when sun is below horizon. +// Made atmos_refract input a requirement for all "functions". +// Changed atmos_refract bound check from +/- 10 to +/- 5 degrees. +// Revised 07-NOV-2006 Andreas +// Corrected 3 earth periodic terms in the L_TERMS array. +// Corrected 2 earth periodic terms in the R_TERMS array. +// Revised 10-NOV-2006 Andreas +// Corrected a constant used to calculate topocentric sun declination. +// Put a limit on observer hour angle, so result is between 0 and 360. +// Revised 13-NOV-2006 Andreas +// Corrected calculation of topocentric sun declination. +// Converted all floating point inputs in spa structure to doubles. +// Revised 27-FEB-2007 Andreas +// Minor correction made as to when atmos. refraction correction is set to zero. +// Revised 21-JAN-2008 Andreas +// Minor change to two variable declarations. +// Revised 12-JAN-2009 Andreas +// Changed timezone bound check from +/-12 to +/-18 hours. +// Revised 14-JAN-2009 Andreas +// Corrected a constant used to calculate ecliptic mean obliquity. +// Revised 01-APR-2013 Andreas +// Replace floor with new integer function for tech. report consistency, no affect on results. +// Add "utility" function prototypes to header file for use with NREL's SAMPA. +// Rename 4 "utility" function names (remove "sun") for clarity with NREL's SAMPA. +// Added delta_ut1 as required input, which the fractional second difference between UT and UTC. +// Time must be input w/o delta_ut1 adjustment, instead of assuming adjustment was pre-applied. +// Revised 10-JUL-2014 Andreas +// Change second in spa_data structure from an integer to double to allow fractional second +// Revised 08-SEP-2014 Andreas +// Corrected description of azm_rotation in header file +// Limited azimuth180 to range of 0 to 360 deg (instead of -180 to 180) for tech report consistency +// Changed all variables names from azimuth180 to azimuth_astro +// Renamed 2 "utility" function names for consistency +/////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include "spa.h" + +#define PI 3.1415926535897932384626433832795028841971 +#define SUN_RADIUS 0.26667 + +#define L_COUNT 6 +#define B_COUNT 2 +#define R_COUNT 5 +#define Y_COUNT 63 + +#define L_MAX_SUBCOUNT 64 +#define B_MAX_SUBCOUNT 5 +#define R_MAX_SUBCOUNT 40 + +enum {TERM_A, TERM_B, TERM_C, TERM_COUNT}; +enum {TERM_X0, TERM_X1, TERM_X2, TERM_X3, TERM_X4, TERM_X_COUNT}; +enum {TERM_PSI_A, TERM_PSI_B, TERM_EPS_C, TERM_EPS_D, TERM_PE_COUNT}; +enum {JD_MINUS, JD_ZERO, JD_PLUS, JD_COUNT}; +enum {SUN_TRANSIT, SUN_RISE, SUN_SET, SUN_COUNT}; + +#define TERM_Y_COUNT TERM_X_COUNT + +const int l_subcount[L_COUNT] = {64,34,20,7,3,1}; +const int b_subcount[B_COUNT] = {5,2}; +const int r_subcount[R_COUNT] = {40,10,6,2,1}; + +/////////////////////////////////////////////////// +/// Earth Periodic Terms +/////////////////////////////////////////////////// +const double L_TERMS[L_COUNT][L_MAX_SUBCOUNT][TERM_COUNT]= +{ + { + {175347046.0,0,0}, + {3341656.0,4.6692568,6283.07585}, + {34894.0,4.6261,12566.1517}, + {3497.0,2.7441,5753.3849}, + {3418.0,2.8289,3.5231}, + {3136.0,3.6277,77713.7715}, + {2676.0,4.4181,7860.4194}, + {2343.0,6.1352,3930.2097}, + {1324.0,0.7425,11506.7698}, + {1273.0,2.0371,529.691}, + {1199.0,1.1096,1577.3435}, + {990,5.233,5884.927}, + {902,2.045,26.298}, + {857,3.508,398.149}, + {780,1.179,5223.694}, + {753,2.533,5507.553}, + {505,4.583,18849.228}, + {492,4.205,775.523}, + {357,2.92,0.067}, + {317,5.849,11790.629}, + {284,1.899,796.298}, + {271,0.315,10977.079}, + {243,0.345,5486.778}, + {206,4.806,2544.314}, + {205,1.869,5573.143}, + {202,2.458,6069.777}, + {156,0.833,213.299}, + {132,3.411,2942.463}, + {126,1.083,20.775}, + {115,0.645,0.98}, + {103,0.636,4694.003}, + {102,0.976,15720.839}, + {102,4.267,7.114}, + {99,6.21,2146.17}, + {98,0.68,155.42}, + {86,5.98,161000.69}, + {85,1.3,6275.96}, + {85,3.67,71430.7}, + {80,1.81,17260.15}, + {79,3.04,12036.46}, + {75,1.76,5088.63}, + {74,3.5,3154.69}, + {74,4.68,801.82}, + {70,0.83,9437.76}, + {62,3.98,8827.39}, + {61,1.82,7084.9}, + {57,2.78,6286.6}, + {56,4.39,14143.5}, + {56,3.47,6279.55}, + {52,0.19,12139.55}, + {52,1.33,1748.02}, + {51,0.28,5856.48}, + {49,0.49,1194.45}, + {41,5.37,8429.24}, + {41,2.4,19651.05}, + {39,6.17,10447.39}, + {37,6.04,10213.29}, + {37,2.57,1059.38}, + {36,1.71,2352.87}, + {36,1.78,6812.77}, + {33,0.59,17789.85}, + {30,0.44,83996.85}, + {30,2.74,1349.87}, + {25,3.16,4690.48} + }, + { + {628331966747.0,0,0}, + {206059.0,2.678235,6283.07585}, + {4303.0,2.6351,12566.1517}, + {425.0,1.59,3.523}, + {119.0,5.796,26.298}, + {109.0,2.966,1577.344}, + {93,2.59,18849.23}, + {72,1.14,529.69}, + {68,1.87,398.15}, + {67,4.41,5507.55}, + {59,2.89,5223.69}, + {56,2.17,155.42}, + {45,0.4,796.3}, + {36,0.47,775.52}, + {29,2.65,7.11}, + {21,5.34,0.98}, + {19,1.85,5486.78}, + {19,4.97,213.3}, + {17,2.99,6275.96}, + {16,0.03,2544.31}, + {16,1.43,2146.17}, + {15,1.21,10977.08}, + {12,2.83,1748.02}, + {12,3.26,5088.63}, + {12,5.27,1194.45}, + {12,2.08,4694}, + {11,0.77,553.57}, + {10,1.3,6286.6}, + {10,4.24,1349.87}, + {9,2.7,242.73}, + {9,5.64,951.72}, + {8,5.3,2352.87}, + {6,2.65,9437.76}, + {6,4.67,4690.48} + }, + { + {52919.0,0,0}, + {8720.0,1.0721,6283.0758}, + {309.0,0.867,12566.152}, + {27,0.05,3.52}, + {16,5.19,26.3}, + {16,3.68,155.42}, + {10,0.76,18849.23}, + {9,2.06,77713.77}, + {7,0.83,775.52}, + {5,4.66,1577.34}, + {4,1.03,7.11}, + {4,3.44,5573.14}, + {3,5.14,796.3}, + {3,6.05,5507.55}, + {3,1.19,242.73}, + {3,6.12,529.69}, + {3,0.31,398.15}, + {3,2.28,553.57}, + {2,4.38,5223.69}, + {2,3.75,0.98} + }, + { + {289.0,5.844,6283.076}, + {35,0,0}, + {17,5.49,12566.15}, + {3,5.2,155.42}, + {1,4.72,3.52}, + {1,5.3,18849.23}, + {1,5.97,242.73} + }, + { + {114.0,3.142,0}, + {8,4.13,6283.08}, + {1,3.84,12566.15} + }, + { + {1,3.14,0} + } +}; + +const double B_TERMS[B_COUNT][B_MAX_SUBCOUNT][TERM_COUNT]= +{ + { + {280.0,3.199,84334.662}, + {102.0,5.422,5507.553}, + {80,3.88,5223.69}, + {44,3.7,2352.87}, + {32,4,1577.34} + }, + { + {9,3.9,5507.55}, + {6,1.73,5223.69} + } +}; + +const double R_TERMS[R_COUNT][R_MAX_SUBCOUNT][TERM_COUNT]= +{ + { + {100013989.0,0,0}, + {1670700.0,3.0984635,6283.07585}, + {13956.0,3.05525,12566.1517}, + {3084.0,5.1985,77713.7715}, + {1628.0,1.1739,5753.3849}, + {1576.0,2.8469,7860.4194}, + {925.0,5.453,11506.77}, + {542.0,4.564,3930.21}, + {472.0,3.661,5884.927}, + {346.0,0.964,5507.553}, + {329.0,5.9,5223.694}, + {307.0,0.299,5573.143}, + {243.0,4.273,11790.629}, + {212.0,5.847,1577.344}, + {186.0,5.022,10977.079}, + {175.0,3.012,18849.228}, + {110.0,5.055,5486.778}, + {98,0.89,6069.78}, + {86,5.69,15720.84}, + {86,1.27,161000.69}, + {65,0.27,17260.15}, + {63,0.92,529.69}, + {57,2.01,83996.85}, + {56,5.24,71430.7}, + {49,3.25,2544.31}, + {47,2.58,775.52}, + {45,5.54,9437.76}, + {43,6.01,6275.96}, + {39,5.36,4694}, + {38,2.39,8827.39}, + {37,0.83,19651.05}, + {37,4.9,12139.55}, + {36,1.67,12036.46}, + {35,1.84,2942.46}, + {33,0.24,7084.9}, + {32,0.18,5088.63}, + {32,1.78,398.15}, + {28,1.21,6286.6}, + {28,1.9,6279.55}, + {26,4.59,10447.39} + }, + { + {103019.0,1.10749,6283.07585}, + {1721.0,1.0644,12566.1517}, + {702.0,3.142,0}, + {32,1.02,18849.23}, + {31,2.84,5507.55}, + {25,1.32,5223.69}, + {18,1.42,1577.34}, + {10,5.91,10977.08}, + {9,1.42,6275.96}, + {9,0.27,5486.78} + }, + { + {4359.0,5.7846,6283.0758}, + {124.0,5.579,12566.152}, + {12,3.14,0}, + {9,3.63,77713.77}, + {6,1.87,5573.14}, + {3,5.47,18849.23} + }, + { + {145.0,4.273,6283.076}, + {7,3.92,12566.15} + }, + { + {4,2.56,6283.08} + } +}; + +//////////////////////////////////////////////////////////////// +/// Periodic Terms for the nutation in longitude and obliquity +//////////////////////////////////////////////////////////////// + +const int Y_TERMS[Y_COUNT][TERM_Y_COUNT]= +{ + {0,0,0,0,1}, + {-2,0,0,2,2}, + {0,0,0,2,2}, + {0,0,0,0,2}, + {0,1,0,0,0}, + {0,0,1,0,0}, + {-2,1,0,2,2}, + {0,0,0,2,1}, + {0,0,1,2,2}, + {-2,-1,0,2,2}, + {-2,0,1,0,0}, + {-2,0,0,2,1}, + {0,0,-1,2,2}, + {2,0,0,0,0}, + {0,0,1,0,1}, + {2,0,-1,2,2}, + {0,0,-1,0,1}, + {0,0,1,2,1}, + {-2,0,2,0,0}, + {0,0,-2,2,1}, + {2,0,0,2,2}, + {0,0,2,2,2}, + {0,0,2,0,0}, + {-2,0,1,2,2}, + {0,0,0,2,0}, + {-2,0,0,2,0}, + {0,0,-1,2,1}, + {0,2,0,0,0}, + {2,0,-1,0,1}, + {-2,2,0,2,2}, + {0,1,0,0,1}, + {-2,0,1,0,1}, + {0,-1,0,0,1}, + {0,0,2,-2,0}, + {2,0,-1,2,1}, + {2,0,1,2,2}, + {0,1,0,2,2}, + {-2,1,1,0,0}, + {0,-1,0,2,2}, + {2,0,0,2,1}, + {2,0,1,0,0}, + {-2,0,2,2,2}, + {-2,0,1,2,1}, + {2,0,-2,0,1}, + {2,0,0,0,1}, + {0,-1,1,0,0}, + {-2,-1,0,2,1}, + {-2,0,0,0,1}, + {0,0,2,2,1}, + {-2,0,2,0,1}, + {-2,1,0,2,1}, + {0,0,1,-2,0}, + {-1,0,1,0,0}, + {-2,1,0,0,0}, + {1,0,0,0,0}, + {0,0,1,2,0}, + {0,0,-2,2,2}, + {-1,-1,1,0,0}, + {0,1,1,0,0}, + {0,-1,1,2,2}, + {2,-1,-1,2,2}, + {0,0,3,2,2}, + {2,-1,0,2,2}, +}; + +const double PE_TERMS[Y_COUNT][TERM_PE_COUNT]={ + {-171996,-174.2,92025,8.9}, + {-13187,-1.6,5736,-3.1}, + {-2274,-0.2,977,-0.5}, + {2062,0.2,-895,0.5}, + {1426,-3.4,54,-0.1}, + {712,0.1,-7,0}, + {-517,1.2,224,-0.6}, + {-386,-0.4,200,0}, + {-301,0,129,-0.1}, + {217,-0.5,-95,0.3}, + {-158,0,0,0}, + {129,0.1,-70,0}, + {123,0,-53,0}, + {63,0,0,0}, + {63,0.1,-33,0}, + {-59,0,26,0}, + {-58,-0.1,32,0}, + {-51,0,27,0}, + {48,0,0,0}, + {46,0,-24,0}, + {-38,0,16,0}, + {-31,0,13,0}, + {29,0,0,0}, + {29,0,-12,0}, + {26,0,0,0}, + {-22,0,0,0}, + {21,0,-10,0}, + {17,-0.1,0,0}, + {16,0,-8,0}, + {-16,0.1,7,0}, + {-15,0,9,0}, + {-13,0,7,0}, + {-12,0,6,0}, + {11,0,0,0}, + {-10,0,5,0}, + {-8,0,3,0}, + {7,0,-3,0}, + {-7,0,0,0}, + {-7,0,3,0}, + {-7,0,3,0}, + {6,0,0,0}, + {6,0,-3,0}, + {6,0,-3,0}, + {-6,0,3,0}, + {-6,0,3,0}, + {5,0,0,0}, + {-5,0,3,0}, + {-5,0,3,0}, + {-5,0,3,0}, + {4,0,0,0}, + {4,0,0,0}, + {4,0,0,0}, + {-4,0,0,0}, + {-4,0,0,0}, + {-4,0,0,0}, + {3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, + {-3,0,0,0}, +}; + +/////////////////////////////////////////////// + +double rad2deg(double radians) +{ + return (180.0/PI)*radians; +} + +double deg2rad(double degrees) +{ + return (PI/180.0)*degrees; +} + +int integer(double value) +{ + return value; +} + +double limit_degrees(double degrees) +{ + double limited; + + degrees /= 360.0; + limited = 360.0*(degrees-floor(degrees)); + if (limited < 0) limited += 360.0; + + return limited; +} + +double limit_degrees180pm(double degrees) +{ + double limited; + + degrees /= 360.0; + limited = 360.0*(degrees-floor(degrees)); + if (limited < -180.0) limited += 360.0; + else if (limited > 180.0) limited -= 360.0; + + return limited; +} + +double limit_degrees180(double degrees) +{ + double limited; + + degrees /= 180.0; + limited = 180.0*(degrees-floor(degrees)); + if (limited < 0) limited += 180.0; + + return limited; +} + +double limit_zero2one(double value) +{ + double limited; + + limited = value - floor(value); + if (limited < 0) limited += 1.0; + + return limited; +} + +double limit_minutes(double minutes) +{ + double limited=minutes; + + if (limited < -20.0) limited += 1440.0; + else if (limited > 20.0) limited -= 1440.0; + + return limited; +} + +double dayfrac_to_local_hr(double dayfrac, double timezone) +{ + return 24.0*limit_zero2one(dayfrac + timezone/24.0); +} + +double third_order_polynomial(double a, double b, double c, double d, double x) +{ + return ((a*x + b)*x + c)*x + d; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +int validate_inputs(spa_data *spa) +{ + if ((spa->year < -2000) || (spa->year > 6000)) return 1; + if ((spa->month < 1 ) || (spa->month > 12 )) return 2; + if ((spa->day < 1 ) || (spa->day > 31 )) return 3; + if ((spa->hour < 0 ) || (spa->hour > 24 )) return 4; + if ((spa->minute < 0 ) || (spa->minute > 59 )) return 5; + if ((spa->second < 0 ) || (spa->second >=60 )) return 6; + if ((spa->pressure < 0 ) || (spa->pressure > 5000)) return 12; + if ((spa->temperature <= -273) || (spa->temperature > 6000)) return 13; + if ((spa->delta_ut1 <= -1 ) || (spa->delta_ut1 >= 1 )) return 17; + if ((spa->hour == 24 ) && (spa->minute > 0 )) return 5; + if ((spa->hour == 24 ) && (spa->second > 0 )) return 6; + + if (fabs(spa->delta_t) > 8000 ) return 7; + if (fabs(spa->timezone) > 18 ) return 8; + if (fabs(spa->longitude) > 180 ) return 9; + if (fabs(spa->latitude) > 90 ) return 10; + if (fabs(spa->atmos_refract) > 5 ) return 16; + if ( spa->elevation < -6500000) return 11; + + if ((spa->function == SPA_ZA_INC) || (spa->function == SPA_ALL)) + { + if (fabs(spa->slope) > 360) return 14; + if (fabs(spa->azm_rotation) > 360) return 15; + } + + return 0; +} +/////////////////////////////////////////////////////////////////////////////////////////////// +double julian_day (int year, int month, int day, int hour, int minute, double second, double dut1, double tz) +{ + double day_decimal, julian_day, a; + + day_decimal = day + (hour - tz + (minute + (second + dut1)/60.0)/60.0)/24.0; + + if (month < 3) { + month += 12; + year--; + } + + julian_day = integer(365.25*(year+4716.0)) + integer(30.6001*(month+1)) + day_decimal - 1524.5; + + if (julian_day > 2299160.0) { + a = integer(year/100); + julian_day += (2 - a + integer(a/4)); + } + + return julian_day; +} + +double julian_century(double jd) +{ + return (jd-2451545.0)/36525.0; +} + +double julian_ephemeris_day(double jd, double delta_t) +{ + return jd+delta_t/86400.0; +} + +double julian_ephemeris_century(double jde) +{ + return (jde - 2451545.0)/36525.0; +} + +double julian_ephemeris_millennium(double jce) +{ + return (jce/10.0); +} + +double earth_periodic_term_summation(const double terms[][TERM_COUNT], int count, double jme) +{ + int i; + double sum=0; + + for (i = 0; i < count; i++) + sum += terms[i][TERM_A]*cos(terms[i][TERM_B]+terms[i][TERM_C]*jme); + + return sum; +} + +double earth_values(double term_sum[], int count, double jme) +{ + int i; + double sum=0; + + for (i = 0; i < count; i++) + sum += term_sum[i]*pow(jme, i); + + sum /= 1.0e8; + + return sum; +} + +double earth_heliocentric_longitude(double jme) +{ + double sum[L_COUNT]; + int i; + + for (i = 0; i < L_COUNT; i++) + sum[i] = earth_periodic_term_summation(L_TERMS[i], l_subcount[i], jme); + + return limit_degrees(rad2deg(earth_values(sum, L_COUNT, jme))); + +} + +double earth_heliocentric_latitude(double jme) +{ + double sum[B_COUNT]; + int i; + + for (i = 0; i < B_COUNT; i++) + sum[i] = earth_periodic_term_summation(B_TERMS[i], b_subcount[i], jme); + + return rad2deg(earth_values(sum, B_COUNT, jme)); + +} + +double earth_radius_vector(double jme) +{ + double sum[R_COUNT]; + int i; + + for (i = 0; i < R_COUNT; i++) + sum[i] = earth_periodic_term_summation(R_TERMS[i], r_subcount[i], jme); + + return earth_values(sum, R_COUNT, jme); + +} + +double geocentric_longitude(double l) +{ + double theta = l + 180.0; + + if (theta >= 360.0) theta -= 360.0; + + return theta; +} + +double geocentric_latitude(double b) +{ + return -b; +} + +double mean_elongation_moon_sun(double jce) +{ + return third_order_polynomial(1.0/189474.0, -0.0019142, 445267.11148, 297.85036, jce); +} + +double mean_anomaly_sun(double jce) +{ + return third_order_polynomial(-1.0/300000.0, -0.0001603, 35999.05034, 357.52772, jce); +} + +double mean_anomaly_moon(double jce) +{ + return third_order_polynomial(1.0/56250.0, 0.0086972, 477198.867398, 134.96298, jce); +} + +double argument_latitude_moon(double jce) +{ + return third_order_polynomial(1.0/327270.0, -0.0036825, 483202.017538, 93.27191, jce); +} + +double ascending_longitude_moon(double jce) +{ + return third_order_polynomial(1.0/450000.0, 0.0020708, -1934.136261, 125.04452, jce); +} + +double xy_term_summation(int i, double x[TERM_X_COUNT]) +{ + int j; + double sum=0; + + for (j = 0; j < TERM_Y_COUNT; j++) + sum += x[j]*Y_TERMS[i][j]; + + return sum; +} + +void nutation_longitude_and_obliquity(double jce, double x[TERM_X_COUNT], double *del_psi, + double *del_epsilon) +{ + int i; + double xy_term_sum, sum_psi=0, sum_epsilon=0; + + for (i = 0; i < Y_COUNT; i++) { + xy_term_sum = deg2rad(xy_term_summation(i, x)); + sum_psi += (PE_TERMS[i][TERM_PSI_A] + jce*PE_TERMS[i][TERM_PSI_B])*sin(xy_term_sum); + sum_epsilon += (PE_TERMS[i][TERM_EPS_C] + jce*PE_TERMS[i][TERM_EPS_D])*cos(xy_term_sum); + } + + *del_psi = sum_psi / 36000000.0; + *del_epsilon = sum_epsilon / 36000000.0; +} + +double ecliptic_mean_obliquity(double jme) +{ + double u = jme/10.0; + + return 84381.448 + u*(-4680.93 + u*(-1.55 + u*(1999.25 + u*(-51.38 + u*(-249.67 + + u*( -39.05 + u*( 7.12 + u*( 27.87 + u*( 5.79 + u*2.45))))))))); +} + +double ecliptic_true_obliquity(double delta_epsilon, double epsilon0) +{ + return delta_epsilon + epsilon0/3600.0; +} + +double aberration_correction(double r) +{ + return -20.4898 / (3600.0*r); +} + +double apparent_sun_longitude(double theta, double delta_psi, double delta_tau) +{ + return theta + delta_psi + delta_tau; +} + +double greenwich_mean_sidereal_time (double jd, double jc) +{ + return limit_degrees(280.46061837 + 360.98564736629 * (jd - 2451545.0) + + jc*jc*(0.000387933 - jc/38710000.0)); +} + +double greenwich_sidereal_time (double nu0, double delta_psi, double epsilon) +{ + return nu0 + delta_psi*cos(deg2rad(epsilon)); +} + +double geocentric_right_ascension(double lamda, double epsilon, double beta) +{ + double lamda_rad = deg2rad(lamda); + double epsilon_rad = deg2rad(epsilon); + + return limit_degrees(rad2deg(atan2(sin(lamda_rad)*cos(epsilon_rad) - + tan(deg2rad(beta))*sin(epsilon_rad), cos(lamda_rad)))); +} + +double geocentric_declination(double beta, double epsilon, double lamda) +{ + double beta_rad = deg2rad(beta); + double epsilon_rad = deg2rad(epsilon); + + return rad2deg(asin(sin(beta_rad)*cos(epsilon_rad) + + cos(beta_rad)*sin(epsilon_rad)*sin(deg2rad(lamda)))); +} + +double observer_hour_angle(double nu, double longitude, double alpha_deg) +{ + return limit_degrees(nu + longitude - alpha_deg); +} + +double sun_equatorial_horizontal_parallax(double r) +{ + return 8.794 / (3600.0 * r); +} + +void right_ascension_parallax_and_topocentric_dec(double latitude, double elevation, + double xi, double h, double delta, double *delta_alpha, double *delta_prime) +{ + double delta_alpha_rad; + double lat_rad = deg2rad(latitude); + double xi_rad = deg2rad(xi); + double h_rad = deg2rad(h); + double delta_rad = deg2rad(delta); + double u = atan(0.99664719 * tan(lat_rad)); + double y = 0.99664719 * sin(u) + elevation*sin(lat_rad)/6378140.0; + double x = cos(u) + elevation*cos(lat_rad)/6378140.0; + + delta_alpha_rad = atan2( - x*sin(xi_rad) *sin(h_rad), + cos(delta_rad) - x*sin(xi_rad) *cos(h_rad)); + + *delta_prime = rad2deg(atan2((sin(delta_rad) - y*sin(xi_rad))*cos(delta_alpha_rad), + cos(delta_rad) - x*sin(xi_rad) *cos(h_rad))); + + *delta_alpha = rad2deg(delta_alpha_rad); +} + +double topocentric_right_ascension(double alpha_deg, double delta_alpha) +{ + return alpha_deg + delta_alpha; +} + +double topocentric_local_hour_angle(double h, double delta_alpha) +{ + return h - delta_alpha; +} + +double topocentric_elevation_angle(double latitude, double delta_prime, double h_prime) +{ + double lat_rad = deg2rad(latitude); + double delta_prime_rad = deg2rad(delta_prime); + + return rad2deg(asin(sin(lat_rad)*sin(delta_prime_rad) + + cos(lat_rad)*cos(delta_prime_rad) * cos(deg2rad(h_prime)))); +} + +double atmospheric_refraction_correction(double pressure, double temperature, + double atmos_refract, double e0) +{ + double del_e = 0; + + if (e0 >= -1*(SUN_RADIUS + atmos_refract)) + del_e = (pressure / 1010.0) * (283.0 / (273.0 + temperature)) * + 1.02 / (60.0 * tan(deg2rad(e0 + 10.3/(e0 + 5.11)))); + + return del_e; +} + +double topocentric_elevation_angle_corrected(double e0, double delta_e) +{ + return e0 + delta_e; +} + +double topocentric_zenith_angle(double e) +{ + return 90.0 - e; +} + +double topocentric_azimuth_angle_astro(double h_prime, double latitude, double delta_prime) +{ + double h_prime_rad = deg2rad(h_prime); + double lat_rad = deg2rad(latitude); + + return limit_degrees(rad2deg(atan2(sin(h_prime_rad), + cos(h_prime_rad)*sin(lat_rad) - tan(deg2rad(delta_prime))*cos(lat_rad)))); +} + +double topocentric_azimuth_angle(double azimuth_astro) +{ + return limit_degrees(azimuth_astro + 180.0); +} + +double surface_incidence_angle(double zenith, double azimuth_astro, double azm_rotation, + double slope) +{ + double zenith_rad = deg2rad(zenith); + double slope_rad = deg2rad(slope); + + return rad2deg(acos(cos(zenith_rad)*cos(slope_rad) + + sin(slope_rad )*sin(zenith_rad) * cos(deg2rad(azimuth_astro - azm_rotation)))); +} + +double sun_mean_longitude(double jme) +{ + return limit_degrees(280.4664567 + jme*(360007.6982779 + jme*(0.03032028 + + jme*(1/49931.0 + jme*(-1/15300.0 + jme*(-1/2000000.0)))))); +} + +double eot(double m, double alpha, double del_psi, double epsilon) +{ + return limit_minutes(4.0*(m - 0.0057183 - alpha + del_psi*cos(deg2rad(epsilon)))); +} + +double approx_sun_transit_time(double alpha_zero, double longitude, double nu) +{ + return (alpha_zero - longitude - nu) / 360.0; +} + +double sun_hour_angle_at_rise_set(double latitude, double delta_zero, double h0_prime) +{ + double h0 = -99999; + double latitude_rad = deg2rad(latitude); + double delta_zero_rad = deg2rad(delta_zero); + double argument = (sin(deg2rad(h0_prime)) - sin(latitude_rad)*sin(delta_zero_rad)) / + (cos(latitude_rad)*cos(delta_zero_rad)); + + if (fabs(argument) <= 1) h0 = limit_degrees180(rad2deg(acos(argument))); + + return h0; +} + +void approx_sun_rise_and_set(double *m_rts, double h0) +{ + double h0_dfrac = h0/360.0; + + m_rts[SUN_RISE] = limit_zero2one(m_rts[SUN_TRANSIT] - h0_dfrac); + m_rts[SUN_SET] = limit_zero2one(m_rts[SUN_TRANSIT] + h0_dfrac); + m_rts[SUN_TRANSIT] = limit_zero2one(m_rts[SUN_TRANSIT]); +} + +double rts_alpha_delta_prime(double *ad, double n) +{ + double a = ad[JD_ZERO] - ad[JD_MINUS]; + double b = ad[JD_PLUS] - ad[JD_ZERO]; + + if (fabs(a) >= 2.0) a = limit_zero2one(a); + if (fabs(b) >= 2.0) b = limit_zero2one(b); + + return ad[JD_ZERO] + n * (a + b + (b-a)*n)/2.0; +} + +double rts_sun_altitude(double latitude, double delta_prime, double h_prime) +{ + double latitude_rad = deg2rad(latitude); + double delta_prime_rad = deg2rad(delta_prime); + + return rad2deg(asin(sin(latitude_rad)*sin(delta_prime_rad) + + cos(latitude_rad)*cos(delta_prime_rad)*cos(deg2rad(h_prime)))); +} + +double sun_rise_and_set(double *m_rts, double *h_rts, double *delta_prime, double latitude, + double *h_prime, double h0_prime, int sun) +{ + return m_rts[sun] + (h_rts[sun] - h0_prime) / + (360.0*cos(deg2rad(delta_prime[sun]))*cos(deg2rad(latitude))*sin(deg2rad(h_prime[sun]))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Calculate required SPA parameters to get the right ascension (alpha) and declination (delta) +// Note: JD must be already calculated and in structure +//////////////////////////////////////////////////////////////////////////////////////////////// +void calculate_geocentric_sun_right_ascension_and_declination(spa_data *spa) +{ + double x[TERM_X_COUNT]; + + spa->jc = julian_century(spa->jd); + + spa->jde = julian_ephemeris_day(spa->jd, spa->delta_t); + spa->jce = julian_ephemeris_century(spa->jde); + spa->jme = julian_ephemeris_millennium(spa->jce); + + spa->l = earth_heliocentric_longitude(spa->jme); + spa->b = earth_heliocentric_latitude(spa->jme); + spa->r = earth_radius_vector(spa->jme); + + spa->theta = geocentric_longitude(spa->l); + spa->beta = geocentric_latitude(spa->b); + + x[TERM_X0] = spa->x0 = mean_elongation_moon_sun(spa->jce); + x[TERM_X1] = spa->x1 = mean_anomaly_sun(spa->jce); + x[TERM_X2] = spa->x2 = mean_anomaly_moon(spa->jce); + x[TERM_X3] = spa->x3 = argument_latitude_moon(spa->jce); + x[TERM_X4] = spa->x4 = ascending_longitude_moon(spa->jce); + + nutation_longitude_and_obliquity(spa->jce, x, &(spa->del_psi), &(spa->del_epsilon)); + + spa->epsilon0 = ecliptic_mean_obliquity(spa->jme); + spa->epsilon = ecliptic_true_obliquity(spa->del_epsilon, spa->epsilon0); + + spa->del_tau = aberration_correction(spa->r); + spa->lamda = apparent_sun_longitude(spa->theta, spa->del_psi, spa->del_tau); + spa->nu0 = greenwich_mean_sidereal_time (spa->jd, spa->jc); + spa->nu = greenwich_sidereal_time (spa->nu0, spa->del_psi, spa->epsilon); + + spa->alpha = geocentric_right_ascension(spa->lamda, spa->epsilon, spa->beta); + spa->delta = geocentric_declination(spa->beta, spa->epsilon, spa->lamda); +} + +//////////////////////////////////////////////////////////////////////// +// Calculate Equation of Time (EOT) and Sun Rise, Transit, & Set (RTS) +//////////////////////////////////////////////////////////////////////// + +void calculate_eot_and_sun_rise_transit_set(spa_data *spa) +{ + spa_data sun_rts; + double nu, m, h0, n; + double alpha[JD_COUNT], delta[JD_COUNT]; + double m_rts[SUN_COUNT], nu_rts[SUN_COUNT], h_rts[SUN_COUNT]; + double alpha_prime[SUN_COUNT], delta_prime[SUN_COUNT], h_prime[SUN_COUNT]; + double h0_prime = -1*(SUN_RADIUS + spa->atmos_refract); + int i; + + sun_rts = *spa; + m = sun_mean_longitude(spa->jme); + spa->eot = eot(m, spa->alpha, spa->del_psi, spa->epsilon); + + sun_rts.hour = sun_rts.minute = sun_rts.second = 0; + sun_rts.delta_ut1 = sun_rts.timezone = 0.0; + + sun_rts.jd = julian_day (sun_rts.year, sun_rts.month, sun_rts.day, sun_rts.hour, + sun_rts.minute, sun_rts.second, sun_rts.delta_ut1, sun_rts.timezone); + + calculate_geocentric_sun_right_ascension_and_declination(&sun_rts); + nu = sun_rts.nu; + + sun_rts.delta_t = 0; + sun_rts.jd--; + for (i = 0; i < JD_COUNT; i++) { + calculate_geocentric_sun_right_ascension_and_declination(&sun_rts); + alpha[i] = sun_rts.alpha; + delta[i] = sun_rts.delta; + sun_rts.jd++; + } + + m_rts[SUN_TRANSIT] = approx_sun_transit_time(alpha[JD_ZERO], spa->longitude, nu); + h0 = sun_hour_angle_at_rise_set(spa->latitude, delta[JD_ZERO], h0_prime); + + if (h0 >= 0) { + + approx_sun_rise_and_set(m_rts, h0); + + for (i = 0; i < SUN_COUNT; i++) { + + nu_rts[i] = nu + 360.985647*m_rts[i]; + + n = m_rts[i] + spa->delta_t/86400.0; + alpha_prime[i] = rts_alpha_delta_prime(alpha, n); + delta_prime[i] = rts_alpha_delta_prime(delta, n); + + h_prime[i] = limit_degrees180pm(nu_rts[i] + spa->longitude - alpha_prime[i]); + + h_rts[i] = rts_sun_altitude(spa->latitude, delta_prime[i], h_prime[i]); + } + + spa->srha = h_prime[SUN_RISE]; + spa->ssha = h_prime[SUN_SET]; + spa->sta = h_rts[SUN_TRANSIT]; + + spa->suntransit = dayfrac_to_local_hr(m_rts[SUN_TRANSIT] - h_prime[SUN_TRANSIT] / 360.0, + spa->timezone); + + spa->sunrise = dayfrac_to_local_hr(sun_rise_and_set(m_rts, h_rts, delta_prime, + spa->latitude, h_prime, h0_prime, SUN_RISE), spa->timezone); + + spa->sunset = dayfrac_to_local_hr(sun_rise_and_set(m_rts, h_rts, delta_prime, + spa->latitude, h_prime, h0_prime, SUN_SET), spa->timezone); + + } else spa->srha= spa->ssha= spa->sta= spa->suntransit= spa->sunrise= spa->sunset= -99999; + +} + +/////////////////////////////////////////////////////////////////////////////////////////// +// Calculate all SPA parameters and put into structure +// Note: All inputs values (listed in header file) must already be in structure +/////////////////////////////////////////////////////////////////////////////////////////// +int spa_calculate(spa_data *spa) +{ + int result; + + result = validate_inputs(spa); + + if (result == 0) + { + spa->jd = julian_day (spa->year, spa->month, spa->day, spa->hour, + spa->minute, spa->second, spa->delta_ut1, spa->timezone); + + calculate_geocentric_sun_right_ascension_and_declination(spa); + + spa->h = observer_hour_angle(spa->nu, spa->longitude, spa->alpha); + spa->xi = sun_equatorial_horizontal_parallax(spa->r); + + right_ascension_parallax_and_topocentric_dec(spa->latitude, spa->elevation, spa->xi, + spa->h, spa->delta, &(spa->del_alpha), &(spa->delta_prime)); + + spa->alpha_prime = topocentric_right_ascension(spa->alpha, spa->del_alpha); + spa->h_prime = topocentric_local_hour_angle(spa->h, spa->del_alpha); + + spa->e0 = topocentric_elevation_angle(spa->latitude, spa->delta_prime, spa->h_prime); + spa->del_e = atmospheric_refraction_correction(spa->pressure, spa->temperature, + spa->atmos_refract, spa->e0); + spa->e = topocentric_elevation_angle_corrected(spa->e0, spa->del_e); + + spa->zenith = topocentric_zenith_angle(spa->e); + spa->azimuth_astro = topocentric_azimuth_angle_astro(spa->h_prime, spa->latitude, + spa->delta_prime); + spa->azimuth = topocentric_azimuth_angle(spa->azimuth_astro); + + if ((spa->function == SPA_ZA_INC) || (spa->function == SPA_ALL)) + spa->incidence = surface_incidence_angle(spa->zenith, spa->azimuth_astro, + spa->azm_rotation, spa->slope); + + if ((spa->function == SPA_ZA_RTS) || (spa->function == SPA_ALL)) + calculate_eot_and_sun_rise_transit_set(spa); + } + + return result; +} +/////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/spa.h b/src/spa.h new file mode 100644 index 0000000..d936742 --- /dev/null +++ b/src/spa.h @@ -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 diff --git a/src/spa_wrapper.c b/src/spa_wrapper.c new file mode 100644 index 0000000..faa3f56 --- /dev/null +++ b/src/spa_wrapper.c @@ -0,0 +1,77 @@ +// src/spa_wrapper.c +#include "spa.h" +#include // 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); +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..8b095b0 --- /dev/null +++ b/test.js @@ -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); + }); \ No newline at end of file