SquareWidget.Astronomy.Core: A C# Code Library

I’m happy to announce the release of SquareWidget.Astronomy.Core, a new code library targeting .NET 8.0 and written in C#, that supports common astronomical calculations and algorithms. The current version is 1.1.0. You can reference the NuGet package at the link above. It has a baseline of functionality and I have plans to add more over time.

Why a library? I’ve been writing code (and snippets of code) over several years to solve various problems in astronomy. When something looked interesting enough to share, I published some of that code on my GitHub repo and on this blog. Recently I was ploughing through the position angle of Saturn’s north pole (Jean Meeus,  Astronomical Algorithms, Chapter 49). After some long debugging sessions, I realized I was using the double data type for everything. Several times I would forget whether a value was in degrees, radians, large angles, reduced angles, or something else entirely. So, I decided I needed a basic code library to bring a little order out of the chaos. The first thing I added were the units of measure. Then planets and calculators came later. I have two design goals in mind for the library: useability and robustness. Certainly, the library should return results accurate to within a few minutes of degrees. But it should also be intuitive and easy to use.

I’ve written more detailed documentation on the GitHub README. Here I’ll just introduce a few of the features. At the heart of the library is a Moment struct. You can pass in date and time parameters, a DateTime instance, or a Julian Day (JD) value:

Moment m1 = new(2024, 1, 31, 9, 36, 0);
Moment m2 = new(2024, 1, 31.4);
Moment m3 = new(2460340.90001);

string s1 = m1.ToDateTime().ToString("yyyy MMM dd HH:mm");
string s2 = m2.ToDateTime().ToString("yyyy MMM dd HH:mm");
string s3 = m3.ToDateTime().ToString("yyyy MMM dd HH:mm");

Console.WriteLine(s1); // 2024 Jan 31 09:36
Console.WriteLine(s2); // 2024 Jan 31 09:36
Console.WriteLine(s3); // 2024 Jan 31 09:36

Once you have a Moment you can get its JD, modified JDE, Day D, or Time T (measure of Julian centuries from epoch J2000.0).

The code library also includes several units of measure with the ability to convert between them. For example, suppose I want to model the Earth’s obliquity of the ecliptic.  I can create a Degrees object by passing in decimal degrees:

Degrees d = new(23.41);
Console.WriteLine(d.ToString()); // 23°.41

Units can be converted to other units. Here I’ll convert to Radians, then a SexesimalAngle, and back to Degrees again:

Degrees d = new(23.41);
Console.WriteLine(d.ToString()); // 23°.41

Radians r = d.ToRadians();
Console.WriteLine(r.ToString()); // 0.4085816

SexigesimalAngle sa = new(d);
Console.WriteLine(sa.ToString()); // +23° 24' 36"

Degrees d1 = new(sa);
Console.WriteLine(d1.ToString()); // 23°.41

Quite often in astronomical calculations we deal with very large angles. Those can be reduced to within the range [0, 360] with the ToReducedAngle function:

Degrees d = new(13512.45);
Console.WriteLine(d.ToString()); // 13512°.45
Console.WriteLine(d.ToReducedAngle().ToString()); // 192°.45

A RightAscension can be constructed with the standard hours, minutes, and seconds (HMS) or decimal degrees:

// passing in HMS
RightAscension ra = new(12, 37, 27);
Console.WriteLine(ra.ToString());   // 12h 37m 27s

// passing decimal degrees
RightAscension ra = new(189.3625);
Console.WriteLine(ra.ToString()); // 12h 37m 27s

Two coordinate systems have been implemented, equitorial and ecliptical,  and you can convert back and forth between them. Suppose we have the equatorial coordinates (α, δ) of Pollux from a star catalog or the wiki and we know Earth’s obliquity of the ecliptic (ε):

// Pollux (β Gem)
RightAscension α = new(7, 45, 18.946);
SexigesimalAngle δ = new(28, 01, 34.26);
Degrees ε = new(23.4392911);

EquitorialCoordinates eqc = new(δ, α, ε);
Console.WriteLine(eqc.α.ToString()); // 7h 45m 18.946s
Console.WriteLine(eqc.δ.ToString()); // +28° 1' 34.26"

We can take those coordinates and convert them to ecliptical coordinates:

EquitorialCoordinates eqc = new(δ, α, ε);
EclipticalCoordinates ec = eqc.ToΕclipticCoordinates();

Console.WriteLine(ec.λ.ToDegrees().ToReducedAngle().ToString()); // 113°.21
Console.WriteLine(ec.β.ToDegrees().ToString());                  //   6°.68

You can instantiate a planet with the new operator or use the factory method. You must pass in a Moment struct either way.

DateTime datetime = new(2028, 7, 1);
Moment moment = new(datetime);

// using new operator
Earth earth = new(moment);

// using factory method for polymorphism
Planet planet = PlanetFactory.Create(PlanetName.Earth, moment);

Once you have a planet you can get its properties which include orbital elements and heliocentric spherical coordinates.

The library has six calculators right now and I plan to add at least a couple more in the next few weeks.

    • Geocentric position calculator
    • Moon phase and dates calculator
    • Nutation calculator
    • Position Angle of Saturn’s semiminor axis calculator
    • Sundial calculator
    • Solar longitude calculator

Consult the README documentation and the unit tests for details. Let me just illustrate one here. Suppose I want to know the four main phases of the moon for the month of August 2024:

DateOnly startDate = new(2024, 7, 1);
DateOnly endDate = new(2024, 9, 30);
DateRange dateRange = new(startDate, endDate);

var list = MoonPhaseDatesCalculator.Calculate(dateRange);

// use a collection expression to get just August
list = [.. list
    .Where(i => i.Moment.Month == 8)
    .OrderBy(o => o.Moment.ToDateTime())];

foreach (var item in list)
{
    Console.WriteLine(item.PhaseName + " on " + 
        item.Moment.ToDateTime().ToString("MM/dd/yyyy hh:mm:ss.fff tt"));
}

This displays:

NewMoon on 08/04/2024 11:12:56.525 AM
FirstQuarter on 08/12/2024 03:19:31.125 PM
FullMoon on 08/19/2024 06:25:37.281 PM
LastQuarter on 08/26/2024 09:27:16.726 AM

What’s next? I’ve got two enhancements open right now. One is to add a feature to calculate solar eclipses and the other is to calculate the positions of the four Galilean moons of Jupiter. Sometime after that I’ll want to address magnitude, planetary perihelion and aphelion, and probably more fun stuff with the Moon. Drop me a line if you have any requests.