UNPKG

natural-time-js

Version:

Natural time is a fresh, elegant, and coherent way of measuring the movements of time here on the Earth. This new time standard is based on common sense and the observation of natural cycles. Learn more: https://naturaltime.app. This JavaScript Class tran

244 lines 10 kB
/** * @module natural-time-js/celestial * @description The cosmic heartbeat of natural time - precise astronomical calculations that connect us to the rhythms of sun and moon. * * While artificial time ignores the sky, natural time is anchored to celestial reality. * This module provides the astronomical foundation that makes natural time truly natural: * - Exact solar positions throughout the day * - Precise lunar phases and cycles * - Solstice and equinox calculations * - Location-aware sunrise, sunset, moonrise and moonset times */ import { NaturalDate } from '../core/NaturalDate.js'; import { Body, Observer, SearchHourAngle, SearchRiseSet, SearchAltitude, MoonPhase, Equator, Horizon, Seasons } from 'astronomy-engine'; import { isValidLatitude, isValidNaturalDate, throwValidationError } from '../utils/validators.js'; /** * Hemisphere identifiers for geographical calculations. */ export var HEMISPHERES; (function (HEMISPHERES) { /** Northern hemisphere (latitude >= 0) */ HEMISPHERES["NORTH"] = "NORTH"; /** Southern hemisphere (latitude < 0) */ HEMISPHERES["SOUTH"] = "SOUTH"; })(HEMISPHERES || (HEMISPHERES = {})); /** * Day numbers marking the start and end of summer season. */ export const SEASONS = { /** Day 91 (typically April 1st) - Start of summer in Northern hemisphere */ SUMMER_START_DAY: 91, /** Day 273 (typically September 30th) - End of summer in Northern hemisphere */ SUMMER_END_DAY: 273 }; /** * Solar altitude thresholds in degrees for different daylight conditions. */ export const ANGLES = { /** Altitude below which astronomical night begins (-12°) */ NIGHT_ALTITUDE: -12, /** Altitude below which golden hour lighting occurs (6°) */ GOLDEN_HOUR_ALTITUDE: 6 }; /** * Cache for astronomical calculations to improve performance. */ const astroCache = new Map(); /** * Clears the internal astronomy cache. * Exposed for testing or advanced usage without exposing the cache itself. */ export function resetAstronomyCache() { astroCache.clear(); } /** * Determines the hemisphere based on latitude. */ const getHemisphere = (latitude) => latitude >= 0 ? HEMISPHERES.NORTH : HEMISPHERES.SOUTH; /** * Determines if the current day is in the summer season based on hemisphere. */ const isSummerSeason = (dayOfYear, latitude) => { const hemisphere = getHemisphere(latitude); return hemisphere === HEMISPHERES.NORTH ? (dayOfYear >= SEASONS.SUMMER_START_DAY && dayOfYear <= SEASONS.SUMMER_END_DAY) : (dayOfYear <= SEASONS.SUMMER_START_DAY || dayOfYear >= SEASONS.SUMMER_END_DAY); }; /** * Creates an observer object for astronomical calculations. */ const createObserver = (latitude, longitude) => new Observer(latitude, longitude, 0); /** * Calculates the sun's altitude at a specific natural time and location. */ export function NaturalSunAltitude(naturalDate, latitude) { // Validate inputs if (!isValidNaturalDate(naturalDate)) { throwValidationError('naturalDate', naturalDate, 'NaturalDate instance'); } if (!isValidLatitude(latitude)) { throwValidationError('latitude', latitude, 'number between -90 and 90'); } try { const observer = createObserver(latitude, naturalDate.longitude); const date = new Date(naturalDate.unixTime); const sun = Equator(Body.Sun, date, observer, true, false); return { altitude: Math.max(Horizon(date, observer, sun.ra, sun.dec).altitude, 0), highestAltitude: SearchHourAngle(Body.Sun, observer, 0, new Date(naturalDate.nadir)).hor.altitude }; } catch (error) { throw error; } } /** * Calculates sun events for a specific natural date and location. */ export function NaturalSunEvents(naturalDate, latitude) { // Validate inputs if (!isValidNaturalDate(naturalDate)) { throwValidationError('naturalDate', naturalDate, 'NaturalDate instance'); } if (!isValidLatitude(latitude)) { throwValidationError('latitude', latitude, 'number between -90 and 90'); } const cacheKey = `SUN_${naturalDate.toDateString()}_${latitude}_${naturalDate.longitude}`; try { const cachedResult = astroCache.get(cacheKey); if (cachedResult && 'sunrise' in cachedResult) { return cachedResult; } const observer = createObserver(latitude, naturalDate.longitude); const nadir = new Date(naturalDate.nadir); const isSummer = isSummerSeason(naturalDate.dayOfYear, latitude); // Helper function to handle edge cases const getEventTime = (searchResult, isSummerDefault) => { if (!searchResult) { return isSummer ? (isSummerDefault ? 360 : 0) : 180; } return naturalDate.getTimeOfEvent(searchResult.date) || 0; }; const events = { sunrise: getEventTime(SearchRiseSet(Body.Sun, observer, +1, nadir, 1), false), sunset: getEventTime(SearchRiseSet(Body.Sun, observer, -1, nadir, 1), true), nightStart: getEventTime(SearchAltitude(Body.Sun, observer, -1, nadir, 2, ANGLES.NIGHT_ALTITUDE), true), nightEnd: getEventTime(SearchAltitude(Body.Sun, observer, +1, nadir, 2, ANGLES.NIGHT_ALTITUDE), false), morningGoldenHour: getEventTime(SearchAltitude(Body.Sun, observer, +1, nadir, 2, ANGLES.GOLDEN_HOUR_ALTITUDE), false), eveningGoldenHour: getEventTime(SearchAltitude(Body.Sun, observer, -1, nadir, 2, ANGLES.GOLDEN_HOUR_ALTITUDE), true) }; astroCache.set(cacheKey, events); return events; } catch (error) { throw error; } } /** * Calculates the moon's position and phase. */ export function NaturalMoonPosition(naturalDate, latitude) { // Validate inputs if (!isValidNaturalDate(naturalDate)) { throwValidationError('naturalDate', naturalDate, 'NaturalDate instance'); } if (!isValidLatitude(latitude)) { throwValidationError('latitude', latitude, 'number between -90 and 90'); } try { const observer = createObserver(latitude, naturalDate.longitude); const date = new Date(naturalDate.unixTime); const moon = Equator(Body.Moon, date, observer, true, false); return { altitude: Math.max(Horizon(date, observer, moon.ra, moon.dec).altitude, 0), phase: MoonPhase(date), }; } catch (error) { throw error; } } /** * Calculates moon events for a specific natural date and location. */ export function NaturalMoonEvents(naturalDate, latitude) { // Validate inputs if (!isValidNaturalDate(naturalDate)) { throwValidationError('naturalDate', naturalDate, 'NaturalDate instance'); } if (!isValidLatitude(latitude)) { throwValidationError('latitude', latitude, 'number between -90 and 90'); } const cacheKey = `MOON_${naturalDate.toDateString()}_${latitude}_${naturalDate.longitude}`; try { const cachedResult = astroCache.get(cacheKey); if (cachedResult && 'moonrise' in cachedResult) { return cachedResult; } const observer = createObserver(latitude, naturalDate.longitude); const nadir = new Date(naturalDate.nadir); // Helper function to handle edge cases const getEventTime = (searchResult) => { if (!searchResult) return 0; return naturalDate.getTimeOfEvent(searchResult.date) || 0; }; const events = { moonrise: getEventTime(SearchRiseSet(Body.Moon, observer, +1, nadir, 1)), moonset: getEventTime(SearchRiseSet(Body.Moon, observer, -1, nadir, 1)), highestAltitude: SearchHourAngle(Body.Moon, observer, 0, nadir).hor.altitude }; astroCache.set(cacheKey, events); return events; } catch (error) { throw error; } } /** * Calculates the range of sun positions between winter and summer solstices (average mustaches angle) */ export function MustachesRange(naturalDate, latitude) { // Validate inputs if (!isValidNaturalDate(naturalDate)) { throwValidationError('naturalDate', naturalDate, 'NaturalDate instance'); } if (!isValidLatitude(latitude)) { throwValidationError('latitude', latitude, 'number between -90 and 90'); } const currentYear = new Date(naturalDate.unixTime).getFullYear(); const cacheKey = `MUSTACHES_${currentYear}_${latitude}`; try { const cachedResult = astroCache.get(cacheKey); if (cachedResult && 'winterSunrise' in cachedResult) { return cachedResult; } const currentSeasons = Seasons(currentYear); const winterSolsticeSunEvents = NaturalSunEvents(new NaturalDate(currentSeasons.dec_solstice.date, 0), latitude); const summerSolsticeSunEvents = NaturalSunEvents(new NaturalDate(currentSeasons.jun_solstice.date, 0), latitude); const averageMustacheAngle = Math.min(Math.max(latitude >= 0 ? (winterSolsticeSunEvents.sunrise - summerSolsticeSunEvents.sunrise + summerSolsticeSunEvents.sunset - winterSolsticeSunEvents.sunset) / 4 : (summerSolsticeSunEvents.sunrise - winterSolsticeSunEvents.sunrise + winterSolsticeSunEvents.sunset - summerSolsticeSunEvents.sunset) / 4, 0), 90); const result = { winterSunrise: winterSolsticeSunEvents.sunrise, winterSunset: winterSolsticeSunEvents.sunset, summerSunrise: summerSolsticeSunEvents.sunrise, summerSunset: summerSolsticeSunEvents.sunset, averageMustacheAngle }; astroCache.set(cacheKey, result); return result; } catch (error) { return { winterSunrise: 0, winterSunset: 0, summerSunrise: 0, summerSunset: 0, averageMustacheAngle: 0 }; } } //# sourceMappingURL=celestial.js.map