From c1814c278393d210055bfd3dc20429ef634799d4 Mon Sep 17 00:00:00 2001 From: Ali Camarata Date: Mon, 13 Nov 2023 21:34:55 +0700 Subject: [PATCH] added getMoon functions --- CHANGELOG.md | 4 ++ getMoon.js | 86 ++++++++++++++---------------------------- getMoonIllumination.js | 19 ++++++++++ getMoonPhase.js | 23 +++++++++++ getMoonPosition.js | 26 +++++++++++++ getMoonVisibility.js | 39 +++++++++++++++++++ package-lock.json | 12 ++++-- package.json | 5 ++- test-moon.js | 21 +++++++++++ 9 files changed, 172 insertions(+), 63 deletions(-) create mode 100644 getMoonIllumination.js create mode 100644 getMoonPhase.js create mode 100644 getMoonPosition.js create mode 100644 getMoonVisibility.js create mode 100644 test-moon.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 182a037..7882c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,7 @@ All notable changes to this project will be documented in this file. - Moved timezone to main args and changed default behavior (major) - Updated test cases and readme to reflect new usage (minor) + +## [1.3.0] - 2013-11-13 + +- Major updates to getMoon with own functions diff --git a/getMoon.js b/getMoon.js index efb7df2..7a37717 100644 --- a/getMoon.js +++ b/getMoon.js @@ -1,65 +1,35 @@ -const { getEarthSunDistance } = require('./getEarthSunDistance'); +const { getMoonPhase } = require('./getMoonPhase'); +const { getMoonPosition } = require('./getMoonPosition'); +const { getMoonIllumination } = require('./getMoonIllumination'); +const { getMoonVisibility } = require('./getMoonVisibility'); -function getMoon(date, accurate = true) { - const PI = Math.PI; - const rad = PI / 180; - const e = rad * 23.4397; // obliquity of the Earth +/** + * Calculates detailed moon visibility information. + * + * @param {Date} date - The date for which to calculate moon data. + * @param {number} [latitude=0] - Observer's latitude in decimal degrees. + * @param {number} [longitude=0] - Observer's longitude in decimal degrees. + * @param {number} [elevation=50] - Observer's elevation in meters above sea level. Default is 50 meters. + * @param {number} [temp=15] - Ambient temperature in degrees Celsius. Default is 15°C. + * @param {number} [pressure=1013.25] - Atmospheric pressure in hPa. Default is 1013.25 hPa (average sea level pressure). + * @param {number} [humidity=50] - Humidity in percentage. Default is 50%. + * @param {number} [clouds=0] - Cloudiness in percentage. Default is 0% (clear sky). + * @returns {Object} An object containing moon details: phase, position, illumination, and visibility. + */ +function getMoon(date, latitude = 0, longitude = 0, elevation = 50, temp = 15, pressure = 1013.25, humidity = 50, clouds = 0) { + const phase = getMoonPhase(date); + const position = getMoonPosition(date, latitude, longitude); + const illumination = getMoonIllumination(date); - function toDays(date) { - return (date - new Date(2000, 0, 1)) / 86400000; - } - - function rightAscension(l, b) { - return Math.atan2(Math.sin(l) * Math.cos(e) - Math.tan(b) * Math.sin(e), Math.cos(l)); - } - - function declination(l, b) { - return Math.asin(Math.sin(b) * Math.cos(e) + Math.cos(b) * Math.sin(e) * Math.sin(l)); - } - - function sunCoords(d) { - const M = rad * (357.5291 + 0.98560028 * d); - const L = rad * (280.1470 + 360.9856235 * d) + (1.9148 - 0.004817 * d / 36525) * Math.sin(M) + 0.019993 - 0.000101 * d / 36525 * Math.cos(M); - return { - dec: declination(L, 0), - ra: rightAscension(L, 0) - }; - } - - function moonCoords(d) { - const L = rad * (218.316 + 13.176396 * d); - const M = rad * (134.963 + 13.064993 * d); - const F = rad * (93.272 + 13.229350 * d); - - const l = L + rad * 6.289 * Math.sin(M); // Moon's mean longitude - const b = rad * 5.128 * Math.sin(F); // Moon's mean latitude - const dt = 385001 - 20905 * Math.cos(M); // Distance to the moon in km - - return { - ra: rightAscension(l, b), - dec: declination(l, b), - dist: dt - }; - } - - const d = toDays(date); - const s = sunCoords(d); - const m = moonCoords(d); - - // distance from Earth to Sun in km - const sdist = accurate ? getEarthSunDistance(date) : 149598000; - - const phi = Math.acos(Math.sin(s.dec) * Math.sin(m.dec) + Math.cos(s.dec) * Math.cos(m.dec) * Math.cos(s.ra - m.ra)); - const inc = Math.atan2(sdist * Math.sin(phi), m.dist - sdist * Math.cos(phi)); - const angle = Math.atan2(Math.cos(s.dec) * Math.sin(s.ra - m.ra), Math.sin(s.dec) * Math.cos(m.dec) - Math.cos(s.dec) * Math.sin(m.dec) * Math.cos(s.ra - m.ra)); + // Calculate visibility considering all factors + const visibility = getMoonVisibility(phase, position, illumination, elevation, temp, pressure, humidity, clouds); return { - fraction: (1 + Math.cos(inc)) / 2, - phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, - angle: angle + phase, + position, + illumination, + visibility }; } -module.exports = { - getMoon -}; +module.exports = { getMoon }; diff --git a/getMoonIllumination.js b/getMoonIllumination.js new file mode 100644 index 0000000..d936b6f --- /dev/null +++ b/getMoonIllumination.js @@ -0,0 +1,19 @@ +// Import the SunCalc library +const suncalc = require('suncalc'); + +/** + * Calculates the moon's illumination for a given phase. + * @param {number} phase - The phase of the moon, from 0 (new moon) to 1 (full moon). + * @returns {Object} The moon's illumination details. + */ +function getMoonIllumination(date) { + const illuminationDetails = suncalc.getMoonIllumination(date); + + return { + fraction: illuminationDetails.fraction, // Illuminated fraction of the moon; 0 = new moon, 1 = full moon + phase: illuminationDetails.phase, // Moon phase (0 to 1) + angle: illuminationDetails.angle // Angle in radians of the moon's bright limb + }; +} + +module.exports = { getMoonIllumination }; diff --git a/getMoonPhase.js b/getMoonPhase.js new file mode 100644 index 0000000..dd1ad4a --- /dev/null +++ b/getMoonPhase.js @@ -0,0 +1,23 @@ +/** + * Calculates the current phase of the moon as a fraction. + * @param {Date} date - The date for which to calculate the moon phase. + * @returns {number} The moon phase as a fraction from 0 (new moon) to just under 1 (end of lunar cycle). + */ +function getMoonPhase(date) { + const synodicMonth = 29.530588861; // Average length of a synodic month in days + // Most recent known new moon: November 13, 2023, 03:27 AM + const knownNewMoon = new Date(Date.UTC(2023, 10, 13, 3, 27, 0)); + + // Convert both dates to the number of milliseconds since Unix Epoch and find the difference + const diffInMilliseconds = date - knownNewMoon; + + // Convert the difference to days + const diffInDays = diffInMilliseconds / 1000 / 60 / 60 / 24; + + // Calculate the phase as a fraction of the synodic month + const phase = (diffInDays % synodicMonth) / synodicMonth; + + return phase; +} + +module.exports = { getMoonPhase }; diff --git a/getMoonPosition.js b/getMoonPosition.js new file mode 100644 index 0000000..47f0037 --- /dev/null +++ b/getMoonPosition.js @@ -0,0 +1,26 @@ +// Import the SunCalc library +const suncalc = require('suncalc'); + +/** + * Calculates detailed moon position information. + * @param {Date} date - The date and time for which to calculate the position. + * @param {number} latitude - Observer's latitude in decimal degrees. + * @param {number} longitude - Observer's longitude in decimal degrees. + * @returns {Object} The moon's position (azimuth, altitude), distance, and parallactic angle. + */ +function getMoonPosition(date, latitude, longitude) { + const moonPosition = suncalc.getMoonPosition(date, latitude, longitude); + + // Convert azimuth and altitude from radians to degrees + const azimuth = moonPosition.azimuth * 180 / Math.PI; + const altitude = moonPosition.altitude * 180 / Math.PI; + + return { + azimuth, + altitude, + distance: moonPosition.distance, // distance to moon in kilometers + parallacticAngle: moonPosition.parallacticAngle // parallactic angle in radians + }; +} + +module.exports = { getMoonPosition }; diff --git a/getMoonVisibility.js b/getMoonVisibility.js new file mode 100644 index 0000000..2e67f27 --- /dev/null +++ b/getMoonVisibility.js @@ -0,0 +1,39 @@ +/** + * Calculates detailed moon visibility information. + * + * @param {number} phase - The phase of the moon, from 0 (new moon) to 1 (next full moon). + * @param {Object} position - { azimuth, altitude, distance (km), parallacticAngle (radians) } + * @param {Object} illumination - { fraction, phase, angle } + * @param {number} [elevation=50] - Observer's elevation in meters above sea level. Default is 50 meters. + * @param {number} [temp=15] - Ambient temperature in degrees Celsius. Default is 15°C. + * @param {number} [pressure=1013.25] - Atmospheric pressure in hPa. Default is 1013.25 hPa (average sea level pressure). + * @param {number} [humidity=50] - Humidity in percentage. Default is 50%. + * @param {number} [clouds=0] - Cloudiness in percentage. Default is 0% (clear sky). + * @returns {Object} An object containing moon details: phase, position, illumination, and visibility. + */ + +function getMoonVisibility(phase, position, illumination, elevation = 50, temp = 15, pressure = 1013.25, humidity = 50, clouds = 0) { + + /** Placeholder Simplified Algorithm... + * This is a very simplified algorithm that is not intended to be precise or accurate. + * It is loosely based on average observations from astronomy journals and other sources. + * Using a window of earliest general visibility until near definite visibility is reached. + */ + + const sMonth = 29.530588861; + const phaseHour = 1 / 24 / sMonth; // ~ 0.001410966333 + const startVis = phaseHour * 15; // ~ 0.02116449584 + const endofVis = phaseHour * 30; // ~ 0.04232899168 + const visWindow = endofVis - startVis; + + let visibility = 0; + if (phase > endofVis) { + visibility = 1; + } else if (phase > startVis) { + visibility = (phase - startVis) / visWindow; + } + + return visibility; +} + +module.exports = { getMoonVisibility }; diff --git a/package-lock.json b/package-lock.json index cbc121e..004d1a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,27 @@ { "name": "praycalc", - "version": "1.2.2", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "praycalc", - "version": "1.2.2", + "version": "1.3.0", "license": "ISC", "dependencies": { - "nrel-spa": "^1.2.2" + "nrel-spa": "^1.2.2", + "suncalc": "^1.9.0" } }, "node_modules/nrel-spa": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/nrel-spa/-/nrel-spa-1.2.2.tgz", "integrity": "sha512-2mycHd7PP0l+pPjPvrT6vr+PRHufCFOxsPcpaIOQ9GwhhfzgyTvSsLKPsqC6ZENNU65yq4PetT5XlWj3CoLjiQ==" + }, + "node_modules/suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" } } } diff --git a/package.json b/package.json index 2386f33..8c57ee4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "praycalc", - "version": "1.2.3", + "version": "1.3.0", "description": "Prayer times calculator using nrel-spa and custom formula for Fajr and Isha angles (as well as traditional static angle methods in the All function)", "main": "index.js", "scripts": { @@ -13,6 +13,7 @@ "author": "USF", "license": "ISC", "dependencies": { - "nrel-spa": "^1.2.2" + "nrel-spa": "^1.2.2", + "suncalc": "^1.9.0" } } diff --git a/test-moon.js b/test-moon.js new file mode 100644 index 0000000..0a11817 --- /dev/null +++ b/test-moon.js @@ -0,0 +1,21 @@ +const { getMoon } = require('./index'); + +const date = new Date(); + +/* NYC - minimum params +const city = "New York" +const lat = 40.7128; +const lng = -74.006; +*/ + +// Jakarta - all params +const city = "Jakarta" +const lat = -6.2088 +const lng = 106.8456 + +// Get results +const get = getMoon(date, lat, lng); + +// Print results +console.log(`\nTest: ${city} with current Date():\n`) +console.log("getMoon =", get, "\n");