Skip to main content

Module guide

Module guide 

Source
Expand description

§Guide

A task-by-task walkthrough of skymath, planning a night on M31 (RA 00:42:44.3, Dec +41:16:09) from a site at 52°N, 4°E. Each snippet below is copy-paste runnable on its own. The full sequence — assembled into one program — is examples/plan_night.rs; run cargo run --example plan_night to see it end to end.

Add the crate first:

cargo add skymath time

§Parse a site and a target

Location::parse accepts the FITS SITELAT/SITELONG sexagesimal shape directly. Equatorial::parse_j2000 does the same for a catalogue RA/Dec, with ParseMode::Strict requiring all three sexagesimal fields.

use skymath::{Equatorial, Location, ParseMode, SexaStyle};

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;

    println!(
        "Site {:.4}°N {:.4}°E, {:.0} m",
        site.latitude().degrees(),
        site.longitude().degrees(),
        site.elevation_m()
    );
    println!(
        "M31  {} {}",
        m31.ra_sexagesimal(SexaStyle::default()),
        m31.dec_sexagesimal(SexaStyle::default())
    );
    Ok(())
}

§Which constellation is it in?

constellation walks the IAU boundary table and returns a typed Constellation.

use skymath::{constellation, Equatorial, ParseMode};

fn main() -> skymath::Result<()> {
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;
    let con = constellation(m31);
    println!("M31 is in {con} ({})", con.abbreviation());
    Ok(())
}

§Precess to tonight

Catalogue coordinates are J2000; observer-local quantities need the position at the epoch of the observation. julian_epoch_of turns an instant into that epoch, and precess moves the coordinate there.

use skymath::{julian_epoch_of, precess, Equatorial, ParseMode, SexaStyle};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;
    let tonight = julian_epoch_of(OffsetDateTime::now_utc());
    let of_date = precess(m31, tonight);

    println!(
        "M31 {tonight:?}: {} {}",
        of_date.ra_sexagesimal(SexaStyle::default()),
        of_date.dec_sexagesimal(SexaStyle::default())
    );
    Ok(())
}

§Sidereal time at the site

gmst is Greenwich Mean Sidereal Time; lst adds the site’s east-positive longitude.

use skymath::{gmst, lst, Location};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let now = OffsetDateTime::now_utc();

    println!("GMST {:.5} h", gmst(now).hours());
    println!("LST  {:.5} h", lst(now, site.longitude()).hours());
    Ok(())
}

§Alt/az, airmass, and parallactic angle

alt_az gives the target’s horizontal position; airmass errors once the target is well below the horizon (< −1°) rather than returning a nonsensical number. parallactic_angle is the position angle of the zenith, useful for planning field rotation.

use skymath::{airmass, alt_az, julian_epoch_of, parallactic_angle, precess, Equatorial, Location, ParseMode};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;
    let now = OffsetDateTime::now_utc();
    let of_date = precess(m31, julian_epoch_of(now));

    let h = alt_az(of_date, now, &site);
    println!("Alt/Az {:.2}° / {:.2}°", h.altitude.degrees(), h.azimuth.degrees());

    match airmass(h.altitude) {
        Ok(x) => println!("Airmass {x:.3}"),
        Err(_) => println!("Airmass n/a (below the horizon)"),
    }
    println!(
        "Parallactic {:.1}°",
        parallactic_angle(of_date, now, &site).degrees()
    );
    Ok(())
}

§Transit and an altitude window

transit finds the meridian crossing nearest a given instant. altitude_crossings returns a typed CrossingOutcome so circumpolar and never-visible targets can’t be confused with a normal rise/set pair.

use skymath::{altitude_crossings, julian_epoch_of, precess, transit, Angle, CrossingOutcome, Equatorial, Location, ParseMode};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;
    let now = OffsetDateTime::now_utc();
    let of_date = precess(m31, julian_epoch_of(now));

    println!("Transit {} (UTC)", transit(of_date, now, &site));
    match altitude_crossings(of_date, Angle::from_degrees(30.0), now, &site) {
        CrossingOutcome::AlwaysAbove => println!("30° window: always above"),
        CrossingOutcome::NeverAbove => println!("30° window: never above"),
        CrossingOutcome::Crosses { rise, set } => {
            println!("30° window: {rise} → {set} (UTC)");
        }
    }
    Ok(())
}

§Is it dark enough?

twilight solves for dusk and dawn at a chosen Twilight level, returning a typed TwilightOutcome that distinguishes a normal dark window from a summer night that never gets dark or a polar night that never brightens.

use skymath::{twilight, Location, Twilight, TwilightOutcome};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let now = OffsetDateTime::now_utc();

    match twilight(Twilight::Astronomical, now, &site) {
        TwilightOutcome::Night { dusk, dawn } => println!("Dark sky {dusk} → {dawn} (UTC)"),
        TwilightOutcome::NeverDark => println!("Never astronomically dark tonight"),
        TwilightOutcome::AlwaysDark => println!("Dark around the clock"),
    }
    Ok(())
}

§How close is the Moon?

lunar_separation corrects for topocentric parallax internally, so the separation is accurate for the observing site, not just the Earth’s centre. moon_illumination gives the illuminated fraction of the disk.

use skymath::{lunar_separation, moon_illumination, Equatorial, Location, ParseMode};
use time::OffsetDateTime;

fn main() -> skymath::Result<()> {
    let site = Location::parse("+52 05 32", "+004 18 27", 6.0)?;
    let m31 = Equatorial::parse_j2000("00:42:44.3", "+41:16:09", ParseMode::Strict)?;
    let now = OffsetDateTime::now_utc();

    println!(
        "Moon {:.1}° from M31, {:.0}% illuminated",
        lunar_separation(m31, now, &site).degrees(),
        moon_illumination(now) * 100.0
    );
    Ok(())
}