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.
149 lines (133 loc) • 5.48 kB
text/typescript
/**
* Lagna (Ascendant) Calculation
* Uses Swiss Ephemeris swe_houses to compute the ascendant and house cusps.
*/
import { Ephemeris } from '../../calculations/ephemeris';
import { LagnaInfo, HouseSystemType } from '../types';
import { HOUSE_SYSTEM_CODES, SIGN_NAMES } from './constants';
export interface HouseCuspsResult {
ascendant: number; // tropical ascendant longitude
mc: number; // midheaven
cusps: number[]; // cusps[1..12] (index 0 unused)
}
/**
* Calculate the ascendant (lagna) for a birth moment.
*
* @param utcDate - The birth moment in UTC
* @param latitude - Birth latitude
* @param longitude - Birth longitude
* @param ayanamsa - Ayanamsa value to convert tropical → sidereal
* @param houseSystem - House system type
* @param ephemeris - Ephemeris instance
*/
export function calculateLagna(
utcDate: Date,
latitude: number,
longitude: number,
ayanamsa: number,
houseSystem: HouseSystemType,
ephemeris: Ephemeris,
): { lagna: LagnaInfo; cusps: number[] } {
const cuspsResult = calculateHouseCuspsViaEphemeris(
utcDate, latitude, longitude, houseSystem, ephemeris,
);
// Convert tropical ascendant to sidereal
let siderealAsc = cuspsResult.ascendant - ayanamsa;
if (siderealAsc < 0) siderealAsc += 360;
if (siderealAsc >= 360) siderealAsc -= 360;
const signNumber = Math.floor(siderealAsc / 30) + 1;
const degreeInSign = siderealAsc - (signNumber - 1) * 30;
// Nakshatra from ascendant longitude
const nakshatraInfo = computeNakshatraFromLon(siderealAsc);
// Convert cusps to sidereal
const siderealCusps: number[] = [0]; // index 0 unused
for (let i = 1; i <= 12; i++) {
let sc = (cuspsResult.cusps[i] || 0) - ayanamsa;
if (sc < 0) sc += 360;
if (sc >= 360) sc -= 360;
siderealCusps.push(sc);
}
const lagna: LagnaInfo = {
longitude: Math.round(siderealAsc * 10000) / 10000,
signName: SIGN_NAMES[signNumber] || 'Unknown',
signNumber,
degreeInSign: Math.round(degreeInSign * 100) / 100,
nakshatra: nakshatraInfo.name,
nakshatraNumber: nakshatraInfo.number,
nakshatraPada: nakshatraInfo.pada,
nakshatraLord: nakshatraInfo.lord,
};
return { lagna, cusps: siderealCusps };
}
// ── Internal helpers ─────────────────────────────────────────────────────────
/**
* Call Swiss Ephemeris swe_houses via the Ephemeris class.
*/
function calculateHouseCuspsViaEphemeris(
utcDate: Date,
latitude: number,
longitude: number,
houseSystem: HouseSystemType,
ephemeris: Ephemeris,
): HouseCuspsResult {
try {
const result = ephemeris.calculateHouseCusps(utcDate, latitude, longitude, houseSystem);
return result;
} catch (err) {
// Fallback: simple equal-house calculation based on Sun's longitude
console.warn('House cusp calculation failed, using simple fallback:', err);
return fallbackHouseCusps(utcDate, ephemeris);
}
}
function fallbackHouseCusps(utcDate: Date, ephemeris: Ephemeris): HouseCuspsResult {
// Use Sun position as a very rough ascendant proxy
const sunPos = ephemeris.calculatePosition(utcDate, 'Sun');
const asc = sunPos.longitude;
const cusps: number[] = [0];
for (let i = 0; i < 12; i++) {
cusps.push((asc + i * 30) % 360);
}
return { ascendant: asc, mc: (asc + 270) % 360, cusps };
}
// ── Nakshatra helper (inline to avoid circular deps) ─────────────────────────
const NAKSHATRA_DATA: Array<{ name: string; lord: string }> = [
{ name: 'Ashwini', lord: 'Ketu' },
{ name: 'Bharani', lord: 'Venus' },
{ name: 'Krittika', lord: 'Sun' },
{ name: 'Rohini', lord: 'Moon' },
{ name: 'Mrigashira', lord: 'Mars' },
{ name: 'Ardra', lord: 'Rahu' },
{ name: 'Punarvasu', lord: 'Jupiter' },
{ name: 'Pushya', lord: 'Saturn' },
{ name: 'Ashlesha', lord: 'Mercury' },
{ name: 'Magha', lord: 'Ketu' },
{ name: 'Purva Phalguni', lord: 'Venus' },
{ name: 'Uttara Phalguni', lord: 'Sun' },
{ name: 'Hasta', lord: 'Moon' },
{ name: 'Chitra', lord: 'Mars' },
{ name: 'Swati', lord: 'Rahu' },
{ name: 'Vishakha', lord: 'Jupiter' },
{ name: 'Anuradha', lord: 'Saturn' },
{ name: 'Jyeshtha', lord: 'Mercury' },
{ name: 'Mula', lord: 'Ketu' },
{ name: 'Purva Ashadha', lord: 'Venus' },
{ name: 'Uttara Ashadha', lord: 'Sun' },
{ name: 'Shravana', lord: 'Moon' },
{ name: 'Dhanishtha', lord: 'Mars' },
{ name: 'Shatabhisha', lord: 'Rahu' },
{ name: 'Purva Bhadrapada', lord: 'Jupiter' },
{ name: 'Uttara Bhadrapada', lord: 'Saturn' },
{ name: 'Revati', lord: 'Mercury' },
];
function computeNakshatraFromLon(lon: number): {
name: string; number: number; pada: number; lord: string;
} {
const oneNakshatra = 360 / 27;
const onePada = oneNakshatra / 4;
const normalised = ((lon % 360) + 360) % 360;
const idx = Math.floor(normalised / oneNakshatra);
const posInNak = normalised - idx * oneNakshatra;
const pada = Math.min(Math.floor(posInNak / onePada) + 1, 4);
const nk = NAKSHATRA_DATA[idx] || { name: 'Unknown', lord: 'Unknown' };
return { name: nk.name, number: idx + 1, pada, lord: nk.lord };
}