using Nonemm.Core; using Nonemm.Core.Country; namespace Nonemm.Session; /// Where the station being worked is from here: the beam heading, the heading /// the long way round, how far away it is, and when the sun rises and sets /// there. N1MM writes the same line under its entry window. /// /// The position comes from the grid square if the contest exchanges one and the /// operator has copied it, and from the country file otherwise, which puts the /// station in the middle of its country. That is close enough to point a beam /// with, and it is what N1MM does. public sealed record StationPath( double Heading, double DistanceKm, DateTime? Sunrise, DateTime? Sunset) { /// The heading the long way round, which a big antenna is sometimes better /// off using. public double LongPath => (Heading + 180.0) % 360.0; /// Null when there is nothing to work out a path from: no station in the /// callsign box, no country file, or no position for either end. public static StationPath? For(OperatingPosition session, DateTime nowUtc) { if (Mine(session) is not { } here || Theirs(session) is not { } there) { return null; } (DateTime Rise, DateTime Set)? sun = SunTimes.For(there.Latitude, there.Longitude, nowUtc); return new StationPath( GridSquare.Bearing(here.Latitude, here.Longitude, there.Latitude, there.Longitude), GridSquare.DistanceKm(here.Latitude, here.Longitude, there.Latitude, there.Longitude), sun?.Rise, sun?.Set); } private static (double Latitude, double Longitude)? Mine(OperatingPosition session) => Place(session.Me.GridSquare) ?? Place(session.Session.Countries?.Find(session.Me.Callsign)); private static (double Latitude, double Longitude)? Theirs(OperatingPosition session) { if (session.Entry.Call.Trim().Length == 0) { return null; } return Place(session.Entry.ValueOf(Contests.ExchangeSlot.GridSquare)) ?? Place(session.Session.History.Find(session.Entry.Call)?.GridSquare ?? "") ?? Place(session.Country()); } private static (double Latitude, double Longitude)? Place(string grid) => GridSquare.TryParse(grid, out GridSquare square) ? (square.Latitude, square.Longitude) : null; private static (double Latitude, double Longitude)? Place(CountryLookup? country) => country is null ? null : (country.Latitude, country.Longitude); }