kundli-rs 0.2.0

A Rust library for calculating astrological kundli charts using the Swiss Ephemeris.
Documentation
use swiss_eph::safe::{self, GeoPos, HouseSystem as SwissHouseSystem, Planet, RiseTransFlags};

use crate::kundli::derive::pipeline::ProjectedBase;
use crate::kundli::derive::sign::normalize_longitude;
use crate::kundli::error::DeriveError;

const DAY_RULERS: [Planet; 7] = [
    Planet::Sun,
    Planet::Moon,
    Planet::Mars,
    Planet::Mercury,
    Planet::Jupiter,
    Planet::Venus,
    Planet::Saturn,
];

const NIGHT_RULERS: [Planet; 7] = [
    Planet::Sun,
    Planet::Venus,
    Planet::Mercury,
    Planet::Moon,
    Planet::Saturn,
    Planet::Jupiter,
    Planet::Mars,
];

pub(crate) fn gulika_longitude(projected: &ProjectedBase) -> Result<f64, DeriveError> {
    let geopos = GeoPos {
        longitude: projected.longitude,
        latitude: projected.latitude,
        altitude: 0.0,
    };
    let sunrise = safe::rise_trans(
        projected.jd_ut,
        Planet::Sun,
        None,
        geopos,
        RiseTransFlags::new().with_rise(),
    )
    .map_err(|_| DeriveError::SpecialPointCalculationFailed("gulika_sunrise"))?;
    let sunset = safe::rise_trans(
        projected.jd_ut,
        Planet::Sun,
        None,
        geopos,
        RiseTransFlags::new().with_set(),
    )
    .map_err(|_| DeriveError::SpecialPointCalculationFailed("gulika_sunset"))?;

    let is_day_birth = projected.jd_ut >= sunrise && projected.jd_ut < sunset;
    let day_index = weekday_index(projected.jd_ut);
    let segment = if is_day_birth {
        index_of(DAY_RULERS, Planet::Saturn, day_index)
    } else {
        index_of(NIGHT_RULERS, Planet::Saturn, day_index)
    };

    let span = if is_day_birth {
        sunset - sunrise
    } else {
        let next_sunrise = if projected.jd_ut >= sunset {
            safe::rise_trans(
                projected.jd_ut + 1.0,
                Planet::Sun,
                None,
                geopos,
                RiseTransFlags::new().with_rise(),
            )
            .map_err(|_| DeriveError::SpecialPointCalculationFailed("gulika_next_sunrise"))?
        } else {
            sunrise
        };
        next_sunrise - sunset
    };

    let start = if is_day_birth { sunrise } else { sunset };
    let gulika_jd = start + span * (segment as f64 / 8.0);
    let houses = safe::houses(
        gulika_jd,
        projected.latitude,
        projected.longitude,
        SwissHouseSystem::WholeSign,
    )
    .map_err(|_| DeriveError::SpecialPointCalculationFailed("gulika_ascendant"))?;

    normalize_longitude(houses.ascendant)
}

fn weekday_index(jd_ut: f64) -> usize {
    ((jd_ut.floor() as i64 + 1).rem_euclid(7)) as usize
}

fn index_of(sequence: [Planet; 7], target: Planet, offset: usize) -> usize {
    let rotated = std::array::from_fn::<_, 7, _>(|index| sequence[(offset + index) % 7]);
    rotated
        .iter()
        .position(|planet| *planet == target)
        .unwrap_or(0)
}