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.

Calculate New Moon Dates

In my last post (Calculate Future Solar Eclipses) I showed how to calculate the types of solar eclipses that would occur for a given date range. I took a shortcut and used a NewMoonData class that had a list of all known new moon dates that I culled from the timeanddate.com website. That worked just fine for the purposes of that blog post.

But in the real world who wants to hardcode every single new moon date into the far future? That’s not a realistic solution. I’m following up with an implementation in C# .NET, based on Jean Meeus’ algorithm he adopted from the ELP-2000/82 theory for the Moon. The results show the exact instant of the new moon phase (UTC) for any date from -1999 BCE to +3000 CE, accurate to within a few seconds.

The code is checked into my repo at FindNewMoonConsoleApp. Feel free to clone the code base and take it for a test ride. I won’t do a full walkthrough like with the solar eclipse calculations. I’ll just say that the general approach I took was to start with the first new moon in the J2000.0 epoch (6 Jan 2000) and enumerate over the mean new moon phases that follow. Starting with 6 Jan 2000 the program adds the lunar cycle of 29.5306 days to get 4 Feb 2000. Add the lunar cycle again and you get 5 Mar 2000, and so on for as many months and years as you like.

Sounds easy right? The problem is there are forces of gravity pulling on the Moon and Earth. And none of these bodies move at constant rates. So, the times of the mean phases of the moon just gets us in the ballpark of give or take a day or so. From there we have to apply an algorithm that corrects for all of the perturbations in order to get a more accurate date and time for each new moon.

Suppose I want to know the instant of the new moon in Dec 2022. I run the program and get: 12/23/2022 10:16:47 UTC. The Griffith Observatory, which uses the NASA/JPL Horizons system, rounds it up to 12/23/2022 10:17 UTC.

The program will produce results for every new moon that are accurate to within several seconds for any date range you provide.

Clear skies!

 

 

 

 

 

 

 

 

 

 

Calculate Future Solar Eclipses

According to Herodotus, who wrote The History around 430 BCE, a total solar eclipse interrupted a battle between the Lydians and the Medes:

[J]ust as the battle was growing warm, day was on a sudden changed into night. This event had been foretold by Thales, the Milesian, who forewarned the Ionians of it, fixing for it the very year in which it actually took place. The Medes and Lydians, when they observed the change, ceased fighting, and were alike anxious to have terms of peace agreed on.

(Source: Internet Classic Archives)

This “battle of the eclipse” took place in what is now Turkey, and the eclipse itself could have been the one that occurred on 28 May 585 BCE. Unfortunately, if Thales wrote anything it didn’t survive the centuries. So, we don’t know if he really did predict the eclipse or by what means he did it. All we have is Herodotus to go on. In any case, it’s the first mention in history of anyone predicting an eclipse so it’s interesting on that basis alone.

Wouldn’t it be cool if we could figure out when future solar eclipses occurred for us in the next decade or so? That’s what we’re going to do in this blog post so read on. Continue reading “Calculate Future Solar Eclipses”

Let’s Make a Sundial!

Let’s make a sundial that tells the hour of the day. I know what you’re thinking. Can’t I just go to my local garden store and buy one? Nope. Those are just for decoration only. A real sundial, one that accurately tells the time, must be built for your location (latitude). All sundials are local.

I recommend reading my previous post on building a solar calendar. Also, if you’ve never given much thought to the apparent motion of the sun across the sky, then check out my post on sidereal time. If you know all about sundials and you just want to build one, then read on!

Continue reading “Let’s Make a Sundial!”

Pretty Good Pipeline (PGP)

With apologies to Phil Zimmermann, I’ve put together a “pretty good pipeline” using Azure Pipelines, a multi-stage YAML file, and a couple of ARM templates to deploy a typical .NET Core 3.1 API to an Azure App Service. This won’t be a walkthrough or a “how to” so much as general guidance with links to relevant documentation for more information. From a high-level the pipeline will look something like this diagram:

I am not going to talk about containerization, A/B testing, the latest trends in DevOps, or continuous delivery where a dozen production releases are all in a day’s work.  This is just vanilla guidance for a typical mid-sized shop. If this still interests you then read on.

Continue reading “Pretty Good Pipeline (PGP)”

JSON Variable Substitution in Multi-Stage YAML Pipeline with Azure Key Vault

In my last post I showed how to configure JSON variable substitution with secret variables right in the pipeline. Now in this follow up post I want to expand on that to show how you could instead store your secrets in an Azure Key Vault and reference them in the pipeline. See the publicly visible TimeApi project on Azure DevOps for the source code, ARM templates, and the YAML file.

Continue reading “JSON Variable Substitution in Multi-Stage YAML Pipeline with Azure Key Vault”

JSON Variable Substitution in Multi-Stage YAML Azure Pipeline

If you made it past that long and technical article title then you are here for a purpose. Maybe you’re just getting started with the relatively new YAML experience in Azure Pipelines.  Or maybe you’ve been doing this awhile and found the variables guidance to be less than clear. In any case, in this article I’m going to show how to reference pipeline variables in an Azure Pipelines CI/CD multi-stage YAML file. See also the followup post to this one in which variables are moved into an Azure Key Vault.

Continue reading “JSON Variable Substitution in Multi-Stage YAML Azure Pipeline”

Calculate Distance Between Two Points on Earth

In two-dimensional space (a plane), the shortest distance between two points is a line. The shortest distance between two points on a sphere is the great-circle distance. The shorter arc described by the great circle running through the two points is then calculated by this well known formula below. Given the geographical longitude and latitude \(L_1, \phi_1\) of point P and \(L_2, \phi_2\) of point Q:

$$\cos d = \sin \phi_1 \space \sin \phi_2 + \cos \phi_1 \space \cos \phi_2 \space \cos (L_1 – L_2)$$

Where d is expressed in degrees:

$$d = \arccos (\sin \phi_1 \space \sin \phi_2 + \cos \phi_1 \space \cos \phi_2 \space \cos (L_1 – L_2))$$

By CheCheDaWaff – Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=48187293

I once worked at a logistics company primarily as a database developer. I was asked to calculate distances between various ports in China and the U.S. in order to estimate fuel costs. I implemented the above great-circle algorithm in an Oracle PL/SQL package and it worked just fine. However, the Earth isn’t a sphere. Because of centrifugal force generated from the Earth’s rotation it bulges at the equator. It’s better to say that the Earth is a spheroid or an ellipsoid.

So while the great-circle algorithm is good enough what if we want even greater accuracy? To do that we need to take into account the Earth’s true shape as an ellipsoid. There are two variables we need. We need to know the equatorial radius of the Earth (a) and we need to know its flattening (f). Consider this ellipse that represents the Earth with an exaggerated equatorial bulge:

The equatorial radius AC (or BC) is labeled a and flattening f is the ratio of \(a – b \over a\). These values are constants accepted by the International Astronomical Union (IAU) and I can pull them from the wiki:

a = 6378.140 km

f = 0.00335281

Given these two constants we can now calculate the geodesic distance between two points on the Earth’s surface using Andoyer’s Formula. Here I’ve created the program in C# following Meeus’ Method as described in his book Astronomical Algorithms (p. 85). This method will produce an error of +/- 50 meters.

First there are two helper classes. DoubleExtensions has a method ToRadians that will be necessary for the trigonometric functions:

public static class DoubleExtensions
{
    public static double ToRadians(this double d)
    {
        return d * (Math.PI / 180); // convert degrees to radians
    }

    public static double ToDegrees(this double d)
    {
        return d * (180 / Math.PI); // convert radians to degrees
    }
}

And I created a small struct to wrap the geocoordinates of the two points between which we want to calculate the distance:

public struct GeoCoordinate
{
    public GeoCoordinate(double latitude, double longitude)
    {
        Latitude = latitude;
        Longitude = longitude;
        Name = "Unknown";
    }

    public GeoCoordinate(double latitude, double longitude, string name)
    {
        Latitude = latitude;
        Longitude = longitude;
        Name = name;
    }

    public double Latitude { get; }
    public double Longitude { get; }
    public string Name { get; }
}

With those two helpers in place the main program will calculate the distance between the Observatoire de Paris and the U.S. Naval Observatory in Washington, D.C.

class Program
{
    static void Main(string[] args)
    {
        // constants
        const double a = 6378.140;
        const double f = 0.00335281;

        var p = new GeoCoordinate(
            48.83698, -2.33652, "Observatoire de Paris"); 
        var q = new GeoCoordinate(
            38.921261, 77.06652, "U.S. Naval Observatory");

        double F = (p.Latitude + q.Latitude) / 2;
        double G = (p.Latitude - q.Latitude) / 2;
        double L = (p.Longitude - q.Longitude) / 2;

        double S = Math.Pow(Math.Sin(G.ToRadians()), 2) 
            * Math.Pow(Math.Cos(L.ToRadians()), 2) 
            + Math.Pow(Math.Cos(F.ToRadians()), 2) 
            * Math.Pow(Math.Sin(L.ToRadians()), 2);

        double C = Math.Pow(Math.Cos(G.ToRadians()), 2) 
            * Math.Pow(Math.Cos(L.ToRadians()), 2) 
            + Math.Pow(Math.Sin(F.ToRadians()), 2) 
            * Math.Pow(Math.Sin(L.ToRadians()), 2);

        double O = Math.Atan(Math.Sqrt(S / C));
        double R = Math.Sqrt(S * C) / O;
        double D = 2 * O * a;
        double H1 = (3 * R - 1) / (2 * C);
        double H2 = (3 * R + 1) / (2 * S);

        double s = D * (1 + f * H1 
            * Math.Pow(Math.Sin(F.ToRadians()), 2) 
            * Math.Pow(Math.Cos(G.ToRadians()), 2) 
            - f * H2 
            * Math.Pow(Math.Cos(F.ToRadians()), 2) 
            * Math.Pow(Math.Sin(G.ToRadians()), 2)
            );

        string d = Math.Round(s, 2).ToString();

        Console.WriteLine($"The distance between 
            {p.Name} and {q.Name} is {d} km (+/- 50 m).");
        Console.ReadLine();
    }
}

The output produces the correct result:

The distance between Observatoire de Paris and U.S. Naval Observatory is 6181.63 km (+/- 50 m).

 

 

 

Astronomical Calculations: Sidereal Time

What is Sidereal Time?

The vernal equinox for 2020 was last week (as of this writing) and I had planned on posting this on that day. But there’s a global pandemic spreading through the world right now and, like so many others in North America, I was preoccupied with other things. In any case, in honor of the vernal equinox this post is going to talk about sidereal time. If you’re an amateur astronomer interested in the algorithm for local sidereal time then skip down to the next section. If you’re coming in cold then read on!

Continue reading “Astronomical Calculations: Sidereal Time”

Astronomical Calculations: Moons of Jupiter

Introduction

In this post I’m going to walk through an algorithm for calculating the positions of the four Galilean moons of Jupiter for any given moment in time. This method was developed by Jean Meeus and published in his book Astronomical Algorithms (Willmann-Bell, 2nd Ed., 1998).

All source code is checked into my GitHub repo. You can see the working software in action here: https://moonsofjupiter.azurewebsites.net. If you’re a raw beginner and want to know what you’ve just stumbled upon before diving into this post here are some good places to start:

(1) Ernie Wright provides an introduction with some cool animations that show what Galileo saw at the eyepiece and what he sketched in his own notebook.

(2) Dominic Ford has all kinds of astronomy goodness, including a nice looking corkscrew diagram for the four Galilean moons.

(3) I published an example web site  to  accompany this post. It would be a good idea to play around with it a bit before proceeding so you get an idea of what we’re trying to do.

Continue reading “Astronomical Calculations: Moons of Jupiter”