UNPKG

astrology-insights

Version:

Comprehensive Vedic astrology engine for Node.js — Panchang, birth charts (Kundli), Vimshottari Dasha, divisional charts, dosha analysis, and planetary remedies. Swiss Ephemeris precision, validated against Drik Panchang.

397 lines (396 loc) 19.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateFullPanchang = void 0; const tithi_1 = require("./core/tithi"); const nakshatra_1 = require("./core/nakshatra"); const yoga_1 = require("./core/yoga"); const karana_1 = require("./core/karana"); const rashi_1 = require("./core/rashi"); const constants_1 = require("./core/constants"); const muhurat_1 = require("./timings/muhurat"); const kalam_1 = require("./timings/kalam"); const MOON_PHASES = [ 'New Moon', 'Waxing Crescent', 'First Quarter', 'Waxing Gibbous', 'Full Moon', 'Waning Gibbous', 'Last Quarter', 'Waning Crescent', ]; function getMoonPhaseName(tithiIndex) { // Derive phase from tithi number (more accurate than angular division) // Tithi 0 = Shukla Pratipad (just after new moon) // Tithi 14 = Purnima (full moon) // Tithi 15 = Krishna Pratipad (just after full moon) // Tithi 29 = Amavasya (new moon) if (tithiIndex <= 0) return 'New Moon'; if (tithiIndex <= 3) return 'Waxing Crescent'; if (tithiIndex <= 6) return 'First Quarter'; if (tithiIndex <= 10) return 'Waxing Gibbous'; if (tithiIndex <= 14) return 'Full Moon'; if (tithiIndex <= 18) return 'Waning Gibbous'; if (tithiIndex <= 21) return 'Last Quarter'; if (tithiIndex <= 25) return 'Waning Crescent'; return 'New Moon'; } const RITUS = [ { vedic: 'Vasanta', english: 'Spring' }, { vedic: 'Grishma', english: 'Summer' }, { vedic: 'Varsha', english: 'Monsoon' }, { vedic: 'Sharad', english: 'Autumn' }, { vedic: 'Hemanta', english: 'Pre-Winter' }, { vedic: 'Shishira', english: 'Winter' }, ]; const SOLAR_MONTHS = [ 'Mesha', 'Vrishabha', 'Mithuna', 'Karka', 'Simha', 'Kanya', 'Tula', 'Vrishchika', 'Dhanu', 'Makara', 'Kumbha', 'Meena', ]; const SAMVATSARS = [ 'Prabhava', 'Vibhava', 'Shukla', 'Pramodoota', 'Prajothpatti', 'Angirasa', 'Srimukha', 'Bhava', 'Yuva', 'Dhata', 'Ishvara', 'Bahudhanya', 'Pramathi', 'Vikrama', 'Vrisha', 'Chitrabhanu', 'Svabhanu', 'Tarana', 'Parthiva', 'Vyaya', 'Sarvajit', 'Sarvadhari', 'Virodhi', 'Vikrita', 'Khara', 'Nandana', 'Vijaya', 'Jaya', 'Manmatha', 'Durmukhi', 'Hevilambi', 'Vilambi', 'Vikari', 'Sharvari', 'Plava', 'Shubhakrit', 'Shobhakrit', 'Krodhi', 'Vishvavasu', 'Parabhava', 'Plavanga', 'Kilaka', 'Saumya', 'Sadharana', 'Virodhikrit', 'Paridhaavi', 'Pramadicha', 'Ananda', 'Rakshasa', 'Nala', 'Pingala', 'Kalayukti', 'Siddharthi', 'Raudri', 'Durmathi', 'Dundubhi', 'Rudhirodgari', 'Raktakshi', 'Krodhana', 'Akshaya', ]; function calculateDurations(sunriseStr, sunsetStr) { const [sh, sm] = sunriseStr.split(':').map(Number); const [eh, em] = sunsetStr.split(':').map(Number); const sunriseMin = sh * 60 + sm; const sunsetMin = eh * 60 + em; const dayMins = sunsetMin - sunriseMin; const nightMins = 1440 - dayMins; const midMins = sunriseMin + Math.floor(dayMins / 2); const fmtDuration = (mins) => { const h = Math.floor(mins / 60); const m = mins % 60; return `${h}h ${String(m).padStart(2, '0')}m`; }; const fmtTime = (mins) => { const h = mins % 1440; const hh = Math.floor(h / 60); const mm = h % 60; return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`; }; return { dinamana: fmtDuration(dayMins), ratrimana: fmtDuration(nightMins), madhyahna: fmtTime(midMins), }; } function formatTimeHHMM(date, timezone) { if (!date) return ''; return date.toLocaleTimeString('en-US', { timeZone: timezone, hour: '2-digit', minute: '2-digit', hour12: false }); } function calculateFullPanchang(date, latitude, longitude, timezone) { // Use noon local time as an initial reference for sunrise/sunset calculation. // After computing sunrise, we re-compute planetary positions at sunrise // to match Drik Panchang convention (prevailing tithi/nakshatra at sunrise). const noonDate = typeof date === 'string' ? new Date(date + 'T12:00:00') : date; const location = { latitude, longitude, timezone }; // Helper: compute sidereal sun/moon longitudes for a given Date function computeLongitudes(dt, ayan) { try { const { Ephemeris } = require('./calculations/ephemeris'); const eph = new Ephemeris(); const sunT = eph.calculatePosition(dt, 'Sun'); const moonT = eph.calculatePosition(dt, 'Moon'); return { sunLon: (0, constants_1.normalizeAngle)(sunT.longitude - ayan), moonLon: (0, constants_1.normalizeAngle)(moonT.longitude - ayan), }; } catch { // Fallback: Jean Meeus low-precision const jd = dt.getTime() / 86400000 + 2440587.5; const T = (jd - 2451545.0) / 36525; const L0 = (0, constants_1.normalizeAngle)(280.46646 + 36000.76983 * T); const M = (0, constants_1.normalizeAngle)(357.52911 + 35999.05029 * T); const Mrad = M * Math.PI / 180; const C = (1.914602 - 0.004817 * T) * Math.sin(Mrad) + 0.019993 * Math.sin(2 * Mrad) + 0.000289 * Math.sin(3 * Mrad); const sLon = (0, constants_1.normalizeAngle)((0, constants_1.normalizeAngle)(L0 + C) - ayan); const Lm = (0, constants_1.normalizeAngle)(218.3165 + 481267.8813 * T); const Mm = (0, constants_1.normalizeAngle)(134.9634 + 477198.8676 * T); const F = (0, constants_1.normalizeAngle)(93.2721 + 483202.0175 * T); const MmRad = Mm * Math.PI / 180; const FRad = F * Math.PI / 180; const moonCorr = 6.289 * Math.sin(MmRad) - 1.274 * Math.sin(MmRad - 2 * FRad) + 0.658 * Math.sin(2 * FRad) - 0.186 * Math.sin(Mrad) - 0.114 * Math.sin(2 * FRad) + 0.059 * Math.sin(2 * MmRad) - 0.057 * Math.sin(MmRad - 2 * FRad + Mrad) + 0.053 * Math.sin(MmRad + 2 * FRad) + 0.046 * Math.sin(2 * FRad - Mrad) + 0.041 * Math.sin(MmRad - Mrad); const mLon = (0, constants_1.normalizeAngle)((0, constants_1.normalizeAngle)(Lm + moonCorr) - ayan); return { sunLon: sLon, moonLon: mLon }; } } // Try to use Swiss Ephemeris, fall back to SunCalc-based calculations let sunLon = 0, moonLon = 0; let sunrise = null, sunset = null; let moonrise = null, moonset = null; let ayanamsa = 24.17; // Default Lahiri approximation for 2026 try { const { Ephemeris } = require('./calculations/ephemeris'); const ephemeris = new Ephemeris(); ayanamsa = ephemeris.calculate_lahiri_ayanamsa(noonDate); } catch { // Use default ayanamsa } // Get sunrise/sunset first try { const SunCalc = require('suncalc'); const times = SunCalc.getTimes(noonDate, latitude, longitude); sunrise = times.sunrise; sunset = times.sunset; const moonTimes = SunCalc.getMoonTimes(noonDate, latitude, longitude); moonrise = moonTimes.rise ?? null; moonset = moonTimes.set ?? null; } catch { // No SunCalc available } // Compute longitudes at SUNRISE (Drik Panchang convention: prevailing tithi at sunrise) const sunriseDate = sunrise ?? noonDate; const lons = computeLongitudes(sunriseDate, ayanamsa); sunLon = lons.sunLon; moonLon = lons.moonLon; const tithi = (0, tithi_1.calculateTithi)(sunLon, moonLon); const nakshatra = (0, nakshatra_1.calculateNakshatra)(moonLon); const yoga = (0, yoga_1.calculateYoga)(sunLon, moonLon); const karana = (0, karana_1.calculateKarana)(sunLon, moonLon); const moonSign = (0, rashi_1.calculateRashi)(moonLon); const sunSign = (0, rashi_1.calculateRashi)(sunLon); // ── Transition detection with binary search for exact time ────────────── // Check sunrise vs sunset. If tithi/nakshatra/yoga/karana changed, // binary-search for the exact transition moment. function findTransitionTime(startDate, endDate, getValueFn, startValue, maxIterations = 14) { let lo = startDate.getTime(); let hi = endDate.getTime(); for (let i = 0; i < maxIterations; i++) { const mid = Math.floor((lo + hi) / 2); const midDate = new Date(mid); const lons = computeLongitudes(midDate, ayanamsa); const val = getValueFn(midDate); if (val === startValue) { lo = mid; } else { hi = mid; } } return new Date(hi); } function formatTransitionTime(dt, tz) { try { return dt.toLocaleTimeString('en-IN', { timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: true, }); } catch { const h = dt.getUTCHours(); const m = dt.getUTCMinutes(); return `${h % 12 || 12}:${m < 10 ? '0' + m : m} ${h < 12 ? 'AM' : 'PM'}`; } } let tithi2 = null; let nakshatra2 = null; let yoga2 = null; let karana2 = null; let tithiTransitionTime = ''; let nakshatraTransitionTime = ''; let yogaTransitionTime = ''; let karanaTransitionTime = ''; if (sunset) { try { const sunsetLons = computeLongitudes(sunset, ayanamsa); const tithiAtSunset = (0, tithi_1.calculateTithi)(sunsetLons.sunLon, sunsetLons.moonLon); const nakshatraAtSunset = (0, nakshatra_1.calculateNakshatra)(sunsetLons.moonLon); const yogaAtSunset = (0, yoga_1.calculateYoga)(sunsetLons.sunLon, sunsetLons.moonLon); const karanaAtSunset = (0, karana_1.calculateKarana)(sunsetLons.sunLon, sunsetLons.moonLon); const sunriseDate = sunrise ?? noonDate; if (tithiAtSunset.number !== tithi.number) { tithi2 = tithiAtSunset; const transTime = findTransitionTime(sunriseDate, sunset, (dt) => { const l = computeLongitudes(dt, ayanamsa); return (0, tithi_1.calculateTithi)(l.sunLon, l.moonLon).number; }, tithi.number); tithiTransitionTime = formatTransitionTime(transTime, timezone); } if (nakshatraAtSunset.number !== nakshatra.number) { nakshatra2 = nakshatraAtSunset; const transTime = findTransitionTime(sunriseDate, sunset, (dt) => { const l = computeLongitudes(dt, ayanamsa); return (0, nakshatra_1.calculateNakshatra)(l.moonLon).number; }, nakshatra.number); nakshatraTransitionTime = formatTransitionTime(transTime, timezone); } if (yogaAtSunset.number !== yoga.number) { yoga2 = yogaAtSunset; const transTime = findTransitionTime(sunriseDate, sunset, (dt) => { const l = computeLongitudes(dt, ayanamsa); return (0, yoga_1.calculateYoga)(l.sunLon, l.moonLon).number; }, yoga.number); yogaTransitionTime = formatTransitionTime(transTime, timezone); } if (karanaAtSunset.number !== karana.number) { karana2 = karanaAtSunset; const transTime = findTransitionTime(sunriseDate, sunset, (dt) => { const l = computeLongitudes(dt, ayanamsa); return (0, karana_1.calculateKarana)(l.sunLon, l.moonLon).number; }, karana.number); karanaTransitionTime = formatTransitionTime(transTime, timezone); } } catch { // Transition detection failed, skip } } // Sun Nakshatra const sunNak = (0, nakshatra_1.calculateNakshatra)(sunLon); // Ayana: Uttarayana from Makara Sankranti (Sun enters Capricorn, ~270°) to Sun at ~90° (end of Gemini) // Sidereal: 270°→360°→0°→90° = Uttarayana; 90°→270° = Dakshinayana const ayana = (sunLon >= 270 || sunLon < 90) ? 'Uttarayana' : 'Dakshinayana'; // Ritu (season) based on Sun's sidereal sign // Ritu mapping (North Indian): Pisces-Aries=Vasanta, Taurus-Gemini=Grishma, etc. // Shift by -1 from sign number: sign 12(Pisces)→0, sign 1(Aries)→0, sign 2→1, etc. const rituSignMap = { 12: 0, 1: 0, 2: 1, 3: 1, 4: 2, 5: 2, 6: 3, 7: 3, 8: 4, 9: 4, 10: 5, 11: 5, // Shishira (Capricorn, Aquarius) }; const ritu = RITUS[rituSignMap[sunSign.number] ?? 0]; // Solar month const solarMonth = SOLAR_MONTHS[sunSign.number - 1]; // Samvatsar (60-year cycle) // Vikram new year = Chaitra Shukla Pratipad (day after Chaitra Amavasya) // This falls when Sun is in Pisces (Meena) and Moon is new → Shukla Pratipad // Approximation: if Sun is in Pisces (sign 12) and tithi is in Shukla Paksha, // we're in the new Vikram year. Otherwise check if we've passed the spring new moon. // // More robust: The Vikram year changes when Chaitra Shukla Paksha begins. // Chaitra = lunar month when Sun is in Pisces/Aries. // If tithi is Shukla (waxing) and Sun is in Pisces → new year has started. // If tithi is Krishna (waning) and Sun is in Pisces → still old year (Chaitra Krishna = Phalguna Amanta). const gregYear = noonDate.getFullYear(); const sunInPisces = sunSign.number === 12; // Meena const sunInAries = sunSign.number === 1; // Mesha const isShukla = tithi.paksha === 'Shukla'; // New year starts when: Sun in Pisces + Shukla Paksha (Chaitra Shukla) // OR Sun has moved past Pisces into Aries+ (definitely new year) const newYearStarted = (sunInPisces && isShukla) || (sunSign.number >= 1 && sunSign.number <= 6); // Aries through Virgo = after spring // But Jan-Feb is always before new year (Sun in Capricorn/Aquarius) const month = noonDate.getMonth(); const isEarlyYear = month <= 1; // Jan, Feb always before Hindu new year const vikramSamvat = (newYearStarted && !isEarlyYear) ? gregYear + 57 : gregYear + 56; const shakaSamvat = (newYearStarted && !isEarlyYear) ? gregYear - 78 : gregYear - 79; const samvatsarIndex = ((vikramSamvat - 51) % 60 + 60) % 60; const samvatsar = SAMVATSARS[samvatsarIndex]; // Use the date string to determine weekday (timezone-independent) const varaDateStr = typeof date === 'string' ? date : noonDate.toISOString().split('T')[0]; const varaDate = new Date(varaDateStr + 'T12:00:00Z'); // Noon UTC — safe for any timezone const vara = { name: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][varaDate.getUTCDay()], number: varaDate.getUTCDay(), }; const diff = (0, constants_1.normalizeAngle)(moonLon - sunLon); // Use SunCalc for accurate illumination when available let moonIllumination = Math.round(((1 - Math.cos(diff * Math.PI / 180)) / 2) * 100); try { const SunCalc = require('suncalc'); const illum = SunCalc.getMoonIllumination(noonDate); moonIllumination = Math.round(illum.fraction * 100); } catch { } ; const sunriseStr = formatTimeHHMM(sunrise, timezone); const sunsetStr = formatTimeHHMM(sunset, timezone); const dateStr = noonDate.toISOString().split('T')[0]; // Calculate auspicious muhurats and inauspicious kalams let auspiciousMuhurats = []; let inauspiciousKalams = []; if (sunriseStr && sunsetStr) { try { auspiciousMuhurats = (0, muhurat_1.calculateMuhurats)(sunriseStr, sunsetStr, dateStr, latitude, longitude, timezone); } catch { // Timing calculation failed; return empty array } try { inauspiciousKalams = (0, kalam_1.calculateKalams)(sunriseStr, sunsetStr, dateStr, latitude, longitude, timezone); } catch { // Timing calculation failed; return empty array } } // Dinamana / Ratrimana / Madhyahna const durations = (sunriseStr && sunsetStr) ? calculateDurations(sunriseStr, sunsetStr) : { dinamana: '', ratrimana: '', madhyahna: '' }; return { date: dateStr, location: { lat: latitude, lon: longitude, timezone }, sunrise: sunriseStr, sunset: sunsetStr, moonrise: formatTimeHHMM(moonrise, timezone), moonset: formatTimeHHMM(moonset, timezone), tithi: [ { name: tithi.name, number: tithi.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: tithiTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi.progress }, ...(tithi2 ? [{ name: tithi2.name, number: tithi2.number, startTime: tithiTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi2.progress }] : []), ], nakshatra: [ { name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: nakshatraTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra.progress }, ...(nakshatra2 ? [{ name: nakshatra2.name, number: nakshatra2.number, pada: nakshatra2.pada, lord: nakshatra2.lord, deity: nakshatra2.deity, startTime: nakshatraTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra2.progress }] : []), ], yoga: [ { name: yoga.name, number: yoga.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: yogaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga.progress }, ...(yoga2 ? [{ name: yoga2.name, number: yoga2.number, startTime: yogaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga2.progress }] : []), ], karana: [ { name: karana.name, number: karana.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: karanaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: karana.progress }, ...(karana2 ? [{ name: karana2.name, number: karana2.number, startTime: karanaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: karana2.progress }] : []), ], vara, moonSign, sunSign, moonPhase: { name: getMoonPhaseName(tithi.tithiIndex - 1), illumination: moonIllumination }, paksha: tithi.paksha, auspiciousMuhurats, inauspiciousKalams, sunNakshatra: { name: sunNak.name, number: sunNak.number, pada: sunNak.pada, lord: sunNak.lord, deity: sunNak.deity, startTime: '', endTime: '', progress: sunNak.progress }, ayana, ritu, solarMonth, dinamana: durations.dinamana, ratrimana: durations.ratrimana, madhyahna: durations.madhyahna, samvatsar, vikramSamvat: vikramSamvat, shakaSamvat: shakaSamvat, }; } exports.calculateFullPanchang = calculateFullPanchang;