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.

632 lines (631 loc) 30.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ephemeris = void 0; const swisseph = __importStar(require("swisseph")); const index_1 = require("../utils/index"); const planetary_1 = require("./planetary"); const path = __importStar(require("path")); class Ephemeris { constructor(ephemeris_path) { this.planet_map = { 'Sun': 0, 'Moon': 1, 'Mercury': 2, 'Venus': 3, 'Mars': 4, 'Jupiter': 5, 'Saturn': 6, 'Uranus': 7, 'Neptune': 8, 'Pluto': 9, 'Rahu': 11, 'Ketu': -1 // Special handling for South Node (180° from Rahu) }; this.ephemeris_path = ''; // Use the local ephe directory that contains Swiss Ephemeris files if (!ephemeris_path) { // Try multiple possible paths for the ephemeris files const possible_paths = [ path.join(__dirname, '../../ephe'), path.join(__dirname, '../node_modules/swisseph/ephe'), path.join(process.cwd(), 'ephe'), path.join(process.cwd(), 'node_modules/swisseph/ephe') // CWD node_modules ]; // Use the first existing path this.ephemeris_path = possible_paths.find(p => { try { const fs = require('fs'); return fs.existsSync(p); } catch { return false; } }) || possible_paths[0]; } else { this.ephemeris_path = ephemeris_path; } this.initialize_swiss_eph(); } initialize_swiss_eph() { try { swisseph.swe_set_ephe_path(this.ephemeris_path); } catch (error) { console.warn('Could not set ephemeris path, using default built-in panchang'); } } calculatePosition(date, body) { const jd = this.date_to_julian(date); const planet_id = this.get_planet_id(body); try { let result; if (body === 'Ketu') { // Ketu is 180° opposite to Rahu result = swisseph.swe_calc_ut(jd, swisseph.SE_MEAN_NODE, swisseph.SEFLG_SWIEPH); if (result && 'longitude' in result) { const ketuLongitude = (0, index_1.normalizeAngle)(result.longitude + 180); return { longitude: ketuLongitude, latitude: -result.latitude // Opposite latitude }; } } else { result = swisseph.swe_calc_ut(jd, planet_id, swisseph.SEFLG_SWIEPH); } if (result && 'longitude' in result) { return { longitude: (0, index_1.normalizeAngle)(result.longitude), latitude: result.latitude }; } // Fallback if Swiss Ephemeris fails return this.get_fallback_position(body, date); } catch (error) { console.warn(`Swiss Ephemeris calculation failed for ${body}, using fallback`); return this.get_fallback_position(body, date); } } calculateSiderealPosition(date, body, ayanamsa) { const current_ayanamsa = ayanamsa || this.calculate_lahiri_ayanamsa(date); const tropicalPosition = this.calculatePosition(date, body); return { longitude: (0, index_1.normalizeAngle)(tropicalPosition.longitude - current_ayanamsa), latitude: tropicalPosition.latitude }; } calculate_lahiri_ayanamsa(date) { try { const jd = this.date_to_julian(date); // Set Lahiri ayanamsa (SE_SIDM_LAHIRI = 1) swisseph.swe_set_sid_mode(swisseph.SE_SIDM_LAHIRI, jd, 0); const ayanamsa = swisseph.swe_get_ayanamsa_ut(jd); return ayanamsa || this.get_fallback_lahiri_ayanamsa(date); } catch (error) { return this.get_fallback_lahiri_ayanamsa(date); } } get_fallback_lahiri_ayanamsa(date) { // Simple Lahiri ayanamsa fallback const year = date.getFullYear() + (date.getMonth() + 1) / 12 + date.getDate() / 365.25; const t = (year - 1900.0) / 100.0; return 22.46000 + 1.3915817 * t - 0.0130125 * t * t; } /** * Get all available ayanamsa systems with their degrees for a given date * @param date Date for ayanamsa calculation * @returns Array of ayanamsa information including name, ID, degree, and description */ getAyanamsa(date) { const jd = this.date_to_julian(date); // Swiss Ephemeris Ayanamsa Systems (SE_SIDM constants) const ayanamsa_systems = [ { id: 0, name: 'Fagan/Bradley', description: 'Fagan/Bradley (Western Sidereal)' }, { id: 1, name: 'Lahiri', description: 'Lahiri (Chitrapaksha) - Official Indian Government' }, { id: 2, name: 'De Luce', description: 'De Luce ayanamsa' }, { id: 3, name: 'Raman', description: 'B.V. Raman ayanamsa' }, { id: 4, name: 'Ushashashi', description: 'Ushashashi ayanamsa' }, { id: 5, name: 'Krishnamurti', description: 'Krishnamurti ayanamsa (KP System)' }, { id: 6, name: 'Djwhal Khul', description: 'Djwhal Khul ayanamsa' }, { id: 7, name: 'Yukteshwar', description: 'Sri Yukteshwar ayanamsa' }, { id: 8, name: 'J.N. Bhasin', description: 'J.N. Bhasin ayanamsa' }, { id: 9, name: 'Babylonian (Kugler 1)', description: 'Babylonian ayanamsa (Kugler 1)' }, { id: 10, name: 'Babylonian (Kugler 2)', description: 'Babylonian ayanamsa (Kugler 2)' }, { id: 11, name: 'Babylonian (Kugler 3)', description: 'Babylonian ayanamsa (Kugler 3)' }, { id: 12, name: 'Babylonian (Huber)', description: 'Babylonian ayanamsa (Huber)' }, { id: 13, name: 'Eta Piscium', description: 'Eta Piscium ayanamsa' }, { id: 14, name: 'Aldebaran 15 Tau', description: 'Aldebaran at 15° Taurus' }, { id: 15, name: 'Hipparchos', description: 'Hipparchos ayanamsa' }, { id: 16, name: 'Sassanian', description: 'Sassanian ayanamsa' }, { id: 17, name: 'Galact. Center (Brand)', description: 'Galactic Center ayanamsa (Brand)' }, { id: 18, name: 'J2000', description: 'J2000.0 reference frame' }, { id: 19, name: 'J1900', description: 'J1900.0 reference frame' }, { id: 20, name: 'B1950', description: 'B1950.0 reference frame' }, { id: 21, name: 'Suryasiddhanta', description: 'Suryasiddhanta ayanamsa' }, { id: 22, name: 'Suryasiddhanta (mean Sun)', description: 'Suryasiddhanta (mean Sun)' }, { id: 23, name: 'Aryabhata', description: 'Aryabhata ayanamsa' }, { id: 24, name: 'Aryabhata 522', description: 'Aryabhata 522 CE ayanamsa' }, { id: 25, name: 'Babylonian (Britton)', description: 'Babylonian ayanamsa (Britton)' }, { id: 26, name: 'True Chitra', description: 'True Chitra ayanamsa' }, { id: 27, name: 'True Revati', description: 'True Revati ayanamsa' }, { id: 28, name: 'True Pushya', description: 'True Pushya ayanamsa' }, { id: 29, name: 'Galactic (Gil Brand)', description: 'Galactic Center (Gil Brand)' }, { id: 30, name: 'Galactic Equator (IAU1958)', description: 'Galactic Equator (IAU1958)' }, { id: 31, name: 'Galactic Equator', description: 'Galactic Equator' }, { id: 32, name: 'Galactic Equator (mid-Mula)', description: 'Galactic Equator at mid-Mula' }, { id: 33, name: 'Skydram (Mardyks)', description: 'Skydram ayanamsa (Mardyks)' }, { id: 34, name: 'True Mula', description: 'True Mula ayanamsa' }, { id: 35, name: 'Dhruva Galactic Center', description: 'Dhruva Galactic Center ayanamsa' }, { id: 36, name: 'Aryabhata Mean Sun', description: 'Aryabhata Mean Sun ayanamsa' }, { id: 37, name: 'Lahiri VP285', description: 'Lahiri VP285 ayanamsa' }, { id: 38, name: 'Krishnamurti VP291', description: 'Krishnamurti VP291 ayanamsa' }, { id: 39, name: 'Lahiri ICRC', description: 'Lahiri ICRC ayanamsa' } ]; const results = []; ayanamsa_systems.forEach(system => { try { // Set the ayanamsa mode swisseph.swe_set_sid_mode(system.id, jd, 0); // Get ayanamsa value for the given date const ayanamsaValue = swisseph.swe_get_ayanamsa_ut(jd); results.push({ name: system.name, id: system.id, degree: ayanamsaValue || this.get_fallback_ayanamsa(system.id, date), description: system.description }); } catch (error) { results.push({ name: system.name, id: system.id, degree: this.get_fallback_ayanamsa(system.id, date), description: system.description }); } }); // Sort by degree value for easier comparison results.sort((a, b) => a.degree - b.degree); return results; } /** * Get a specific ayanamsa value by name or ID * @param date Date for calculation * @param ayanamsaId Ayanamsa ID or name * @returns Ayanamsa information */ getSpecificAyanamsa(date, ayanamsa_id) { const allAyanamsas = this.getAyanamsa(date); if (typeof ayanamsa_id === 'number') { return allAyanamsas.find(a => a.id === ayanamsa_id) || null; } else { const exact_match = allAyanamsas.find(a => a.name.toLowerCase() === ayanamsa_id.toLowerCase()); if (exact_match) { return exact_match; } return allAyanamsas.find(a => a.name.toLowerCase().includes(ayanamsa_id.toLowerCase())) || null; } } get_fallback_ayanamsa(systemId, date) { const year = date.getFullYear(); const t = (year - 1900) / 100; // Approximate calculations for different ayanamsa systems switch (systemId) { case 0: // Fagan/Bradley return 24.740 + 1.39 * t - 0.01 * t * t; case 1: // Lahiri return 22.460 + 1.39 * t - 0.01 * t * t; case 3: // Raman return 21.580 + 1.39 * t - 0.01 * t * t; case 5: // Krishnamurti return 23.230 + 1.39 * t - 0.01 * t * t; case 7: // Yukteshwar return 22.460 + 1.39 * t - 0.01 * t * t; default: // Default to Lahiri approximation return 22.460 + 1.39 * t - 0.01 * t * t; } } calculateSunrise(date, location) { try { // Improved sunrise calculation using NOAA Solar Calculator algorithm const year = date.getUTCFullYear(); const month = date.getUTCMonth() + 1; const day = date.getUTCDate(); // Calculate Julian day number const a = Math.floor((14 - month) / 12); const y = year - a; const m = month + 12 * a - 3; const jd = day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; // Calculate day of year const dayOfYear = jd - Math.floor((14 - 1) / 12) * 365 - Math.floor(y / 4) + Math.floor(y / 100) - Math.floor(y / 400) + Math.floor((153 * (1 + 12 * Math.floor((14 - 1) / 12) - 3) + 2) / 5) + 1 - 32045; // More accurate solar calculations const P = Math.asin(0.39795 * Math.cos(0.98563 * (dayOfYear - 173) * Math.PI / 180)); const argumentum = Math.tan(location.latitude * Math.PI / 180) * Math.tan(P); if (Math.abs(argumentum) > 1) { return null; // Polar day or night } const hourAngle = Math.acos(-argumentum) * 180 / Math.PI; const sunrise = 12 - hourAngle / 15 - location.longitude / 15; // Adjust for UTC let sunriseUTC = sunrise; if (sunriseUTC < 0) sunriseUTC += 24; if (sunriseUTC >= 24) sunriseUTC -= 24; const sunriseHours = Math.floor(sunriseUTC); const sunriseMinutes = Math.floor((sunriseUTC - sunriseHours) * 60); const sunriseSeconds = Math.floor(((sunriseUTC - sunriseHours) * 60 - sunriseMinutes) * 60); return new Date(Date.UTC(year, month - 1, day, sunriseHours, sunriseMinutes, sunriseSeconds)); } catch (error) { console.warn('Sunrise calculation failed:', error); // Fallback calculation return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 6, 0, 0, 0); } } calculate_sun_altitude(sunLon, sunLat, location, jd) { // Convert ecliptic coordinates to equatorial const obliquity = 23.43929111; // Mean obliquity of ecliptic for J2000 const sunLonRad = sunLon * Math.PI / 180; const sunLatRad = sunLat * Math.PI / 180; const oblRad = obliquity * Math.PI / 180; // Calculate right ascension and declination const ra = Math.atan2(Math.sin(sunLonRad) * Math.cos(oblRad) - Math.tan(sunLatRad) * Math.sin(oblRad), Math.cos(sunLonRad)); const dec = Math.asin(Math.sin(sunLatRad) * Math.cos(oblRad) + Math.cos(sunLatRad) * Math.sin(oblRad) * Math.sin(sunLonRad)); // Calculate Greenwich Mean Sidereal Time const t = (jd - 2451545.0) / 36525; const gmst0 = 100.46061837 + 36000.770053608 * t + 0.000387933 * t * t - t * t * t / 38710000; const gmst = gmst0 + 15.04106864 * ((jd - Math.floor(jd)) * 24); const lst = (gmst + location.longitude + 360) % 360; // Calculate hour angle const ha = (lst - ra * 180 / Math.PI) * Math.PI / 180; // Calculate altitude const latRad = location.latitude * Math.PI / 180; const altitude = Math.asin(Math.sin(latRad) * Math.sin(dec) + Math.cos(latRad) * Math.cos(dec) * Math.cos(ha)); return altitude * 180 / Math.PI; } calculateMoonrise(date, location) { try { const jd = this.date_to_julian(date); for (let hour = 0; hour < 48; hour += 0.1) { // Check 48 hours for moonrise const testJd = jd - 0.5 + hour / 24; const moonPos = swisseph.swe_calc_ut(testJd, swisseph.SE_MOON, swisseph.SEFLG_SWIEPH); if (moonPos && 'longitude' in moonPos && moonPos.longitude !== undefined) { const altitude = this.calculate_moon_altitude(moonPos.longitude, moonPos.latitude, location, testJd); if (altitude > -0.8333 && hour > 3) { return this.julian_to_date(testJd); } } } return null; } catch (error) { console.warn('Swiss Ephemeris moonrise calculation failed:', error); return null; } } calculateMoonset(date, location) { try { const jd = this.date_to_julian(date); for (let hour = 0; hour < 48; hour += 0.1) { // Check 48 hours for moonset const testJd = jd - 0.5 + hour / 24; const moonPos = swisseph.swe_calc_ut(testJd, swisseph.SE_MOON, swisseph.SEFLG_SWIEPH); if (moonPos && 'longitude' in moonPos && moonPos.longitude !== undefined) { const altitude = this.calculate_moon_altitude(moonPos.longitude, moonPos.latitude, location, testJd); if (altitude < -0.8333 && hour > 3) { return this.julian_to_date(testJd); } } } return null; } catch (error) { console.warn('Swiss Ephemeris moonset calculation failed:', error); return null; } } calculate_moon_altitude(moonLon, moonLat, location, jd) { // This is a simplified calculation and can be improved with more precise models const obliquity = 23.43929111; const moonLonRad = moonLon * Math.PI / 180; const moonLatRad = moonLat * Math.PI / 180; const oblRad = obliquity * Math.PI / 180; const ra = Math.atan2(Math.sin(moonLonRad) * Math.cos(oblRad) - Math.tan(moonLatRad) * Math.sin(oblRad), Math.cos(moonLonRad)); const dec = Math.asin(Math.sin(moonLatRad) * Math.cos(oblRad) + Math.cos(moonLatRad) * Math.sin(oblRad) * Math.sin(moonLonRad)); const t = (jd - 2451545.0) / 36525; const gmst0 = 100.46061837 + 36000.770053608 * t + 0.000387933 * t * t - t * t * t / 38710000; const gmst = gmst0 + 15.04106864 * ((jd - Math.floor(jd)) * 24); const lst = (gmst + location.longitude + 360) % 360; const ha = (lst - ra * 180 / Math.PI) * Math.PI / 180; const latRad = location.latitude * Math.PI / 180; const altitude = Math.asin(Math.sin(latRad) * Math.sin(dec) + Math.cos(latRad) * Math.cos(dec) * Math.cos(ha)); return altitude * 180 / Math.PI; } calculateSunset(date, location) { try { // Improved sunset calculation using NOAA Solar Calculator algorithm const year = date.getUTCFullYear(); const month = date.getUTCMonth() + 1; const day = date.getUTCDate(); // Calculate Julian day number const a = Math.floor((14 - month) / 12); const y = year - a; const m = month + 12 * a - 3; const jd = day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; // Calculate day of year const dayOfYear = jd - Math.floor((14 - 1) / 12) * 365 - Math.floor(y / 4) + Math.floor(y / 100) - Math.floor(y / 400) + Math.floor((153 * (1 + 12 * Math.floor((14 - 1) / 12) - 3) + 2) / 5) + 1 - 32045; // More accurate solar calculations const P = Math.asin(0.39795 * Math.cos(0.98563 * (dayOfYear - 173) * Math.PI / 180)); const argumentum = Math.tan(location.latitude * Math.PI / 180) * Math.tan(P); if (Math.abs(argumentum) > 1) { return null; // Polar day or night } const hourAngle = Math.acos(-argumentum) * 180 / Math.PI; const sunset = 12 + hourAngle / 15 - location.longitude / 15; // Adjust for UTC let sunsetUTC = sunset; if (sunsetUTC < 0) sunsetUTC += 24; if (sunsetUTC >= 24) sunsetUTC -= 24; const sunsetHours = Math.floor(sunsetUTC); const sunsetMinutes = Math.floor((sunsetUTC - sunsetHours) * 60); const sunsetSeconds = Math.floor(((sunsetUTC - sunsetHours) * 60 - sunsetMinutes) * 60); return new Date(Date.UTC(year, month - 1, day, sunsetHours, sunsetMinutes, sunsetSeconds)); } catch (error) { console.warn('Sunset calculation failed:', error); // Fallback calculation return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 18, 0, 0, 0); } } calculateNakshatra(longitude) { const nakshatraNames = [ 'Ashwini', 'Bharani', 'Krittika', 'Rohini', 'Mrigashira', 'Ardra', 'Punarvasu', 'Pushya', 'Ashlesha', 'Magha', 'Purva Phalguni', 'Uttara Phalguni', 'Hasta', 'Chitra', 'Swati', 'Vishakha', 'Anuradha', 'Jyeshtha', 'Mula', 'Purva Ashadha', 'Uttara Ashadha', 'Shravana', 'Dhanishta', 'Shatabhisha', 'Purva Bhadrapada', 'Uttara Bhadrapada', 'Revati' ]; const oneNakshatra = 360 / 27; // 13°20' const onePada = oneNakshatra / 4; // 3°20' const normalizedLon = (0, index_1.normalizeAngle)(longitude); const nakshatraNum = Math.floor(normalizedLon / oneNakshatra) + 1; const remainder = normalizedLon % oneNakshatra; const padaNum = Math.floor(remainder / onePada) + 1; return { nakshatra: nakshatraNum, pada: padaNum, name: nakshatraNames[nakshatraNum - 1] || 'Unknown' }; } date_to_julian(date) { // CRITICAL: Use UTC components to ensure consistent Julian Day calculation // This preserves the exact moment represented by the Date object let year = date.getUTCFullYear(); let month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const hour = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600 + date.getUTCMilliseconds() / 3600000; try { // Use Swiss Ephemeris for accurate Julian Day calculation return swisseph.swe_julday(year, month, day, hour, swisseph.SE_GREG_CAL); } catch (error) { // High-precision fallback Julian Day calculation // Algorithm from Meeus "Astronomical Algorithms" let a, b; if (month <= 2) { year = year - 1; month = month + 12; } a = Math.floor(year / 100); b = 2 - a + Math.floor(a / 4); const jd = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + hour / 24 + b - 1524.5; return jd; } } julian_to_date(jd) { try { const result = swisseph.swe_revjul(jd, swisseph.SE_GREG_CAL); return new Date(result.year, result.month - 1, result.day, Math.floor(result.hour), Math.floor((result.hour % 1) * 60)); } catch (error) { // Fallback conversion return new Date((jd - 2440587.5) * 86400000); } } get_planet_id(body) { return this.planet_map[body] !== undefined ? this.planet_map[body] : 0; } get_fallback_position(body, date) { // Simple fallback using basic orbital elements // Use proper UTC epoch calculation const epoch = new Date(Date.UTC(2000, 0, 1, 12, 0, 0, 0)); // J2000.0 epoch const daysSinceEpoch = (date.getTime() - epoch.getTime()) / 86400000; const positions = { 'Sun': { lon: 280.460, motion: 0.985647 }, 'Moon': { lon: 218.316, motion: 13.176396 }, 'Mercury': { lon: 252.251, motion: 4.092317 }, 'Venus': { lon: 181.980, motion: 1.602136 }, 'Mars': { lon: 355.433, motion: 0.524071 }, 'Jupiter': { lon: 34.351, motion: 0.083056 }, 'Saturn': { lon: 50.078, motion: 0.033371 } }; const body_data = positions[body] || positions['Sun']; const longitude = (0, index_1.normalizeAngle)(body_data.lon + body_data.motion * daysSinceEpoch); return { longitude, latitude: 0 }; } getCurrentPlanets(date = new Date(), ayanamsaId = 1) { const planetary = new planetary_1.Planetary(); const planets = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn']; const positions = []; // Get ayanamsa value for the date const ayanamsaInfo = this.getSpecificAyanamsa(date, ayanamsaId); const ayanamsa = ayanamsaInfo ? ayanamsaInfo.degree : 24.0; // Default to approximate Lahiri for (const planet of planets) { try { const position = this.calculatePosition(date, planet); // Convert to sidereal longitude by subtracting ayanamsa const siderealLongitude = (0, index_1.normalizeAngle)(position.longitude - ayanamsa); // Calculate Rashi and Nakshatra const rashi = planetary.calculateRashi(siderealLongitude); const nakshatra = planetary.calculateNakshatra(siderealLongitude); positions.push({ planet: planet, longitude: siderealLongitude, latitude: position.latitude, rashi: rashi, nakshatra: nakshatra }); } catch (error) { console.warn(`Could not calculate position for ${planet}:`, error); // Add with fallback panchang const fallbackPos = this.get_fallback_position(planet, date); const siderealLongitude = (0, index_1.normalizeAngle)(fallbackPos.longitude - ayanamsa); positions.push({ planet: planet, longitude: siderealLongitude, latitude: fallbackPos.latitude, rashi: planetary.calculateRashi(siderealLongitude), nakshatra: planetary.calculateNakshatra(siderealLongitude) }); } } return positions; } /** * Calculate house cusps and ascendant using Swiss Ephemeris swe_houses. * @param date UTC Date for calculation * @param latitude Geographic latitude * @param longitude Geographic longitude * @param houseSystem House system type ('whole_sign' | 'equal' | 'placidus') * @returns Object with ascendant, mc, and cusps[1..12] */ calculateHouseCusps(date, latitude, longitude, houseSystem) { const jd = this.date_to_julian(date); // Map house system to Swiss Ephemeris code const systemCodes = { whole_sign: 'W', equal: 'E', placidus: 'P', }; const hsys = systemCodes[houseSystem] || 'W'; try { const result = swisseph.swe_houses(jd, latitude, longitude, hsys); if (result && result.house) { // result.house is array of 12 cusp longitudes (0-indexed) // ascendant and mc may be in ascmc array or as direct properties const cusps = [0]; // index 0 is unused for (let i = 0; i < 12; i++) { cusps.push(result.house[i]); } let ascendant; let mc; if (result.ascmc) { ascendant = result.ascmc[0]; mc = result.ascmc[1]; } else { ascendant = result.ascendant || cusps[1]; mc = result.mc || cusps[10] || 0; } return { ascendant, mc, cusps }; } throw new Error('swe_houses returned invalid result'); } catch (error) { // Fallback: use Sun position as rough ascendant proxy console.warn('swe_houses failed, using fallback:', error); const sunPos = this.calculatePosition(date, 'Sun'); const asc = (0, index_1.normalizeAngle)(sunPos.longitude); const cusps = [0]; for (let i = 0; i < 12; i++) { cusps.push((0, index_1.normalizeAngle)(asc + i * 30)); } return { ascendant: asc, mc: (0, index_1.normalizeAngle)(asc + 270), cusps }; } } /** * Calculate position WITH speed (degrees/day) for a celestial body. * Uses SEFLG_SPEED flag. * @param date Date for calculation * @param body Celestial body name * @returns Object with longitude, latitude, and speed */ calculatePositionWithSpeed(date, body) { const jd = this.date_to_julian(date); const planet_id = this.get_planet_id(body); try { let result; if (body === 'Ketu') { result = swisseph.swe_calc_ut(jd, swisseph.SE_MEAN_NODE, swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED); if (result && 'longitude' in result) { return { longitude: (0, index_1.normalizeAngle)(result.longitude + 180), latitude: -result.latitude, speed: -(result.longitudeSpeed || 0), }; } } else { result = swisseph.swe_calc_ut(jd, planet_id, swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED); } if (result && 'longitude' in result) { return { longitude: (0, index_1.normalizeAngle)(result.longitude), latitude: result.latitude, speed: result.longitudeSpeed || 0, }; } throw new Error('swe_calc_ut returned invalid result'); } catch (error) { // Return fallback with zero speed const pos = this.get_fallback_position(body, date); return { longitude: pos.longitude, latitude: pos.latitude, speed: 0 }; } } cleanup() { try { swisseph.swe_close(); } catch (error) { console.warn('Error closing Swiss Ephemeris:', error); } } } exports.Ephemeris = Ephemeris;