Exemplo n.º 1
0
        public static int BeamHeading(Locator start, Locator end)
        {
            ////double R = 6371; // km
            //double dLat = DegreesToRadians(end.Latitude - start.Latitude);
            //double dLon = DegreesToRadians(end.Longitude - start.Longitude);
            ////double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
            ////        Math.Cos(DegreesToRadians(end.Latitude)) * Math.Cos(DegreesToRadians(end.Latitude)) *
            ////        Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
            ////double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
            ////double d = R * c;
            //double y = Math.Sin(dLon) * Math.Cos(end.Latitude);
            //double x = Math.Cos(start.Latitude) * Math.Sin(end.Latitude) -
            //        Math.Sin(start.Latitude) * Math.Cos(end.Latitude) * Math.Cos(dLon);
            //double brng = Math.Atan2(y, x);
            //return ((int)RadiansToDegrees(brng) + 360) % 360;

            double lat1 = DegreesToRadians(start.Latitude);
            double lat2 = DegreesToRadians(end.Latitude);
            double dLon = DegreesToRadians(end.Longitude - start.Longitude);

            double y = Math.Sin(dLon) * Math.Cos(lat2);
            double x = Math.Cos(lat1) * Math.Sin(lat2) -
                    Math.Sin(lat1) * Math.Cos(lat2) * Math.Cos(dLon);
            int degrees = (int)RadiansToDegrees(Math.Atan2(y, x));
            if (degrees < 0)
                return 360 + degrees;
            else
                return degrees;
        }
Exemplo n.º 2
0
 public static bool ValidateLocatorTextbox(TextBox tb)
 {
     if (tb.TextLength != 6)
         return true;
     try
     {
         Locator l = new Locator(tb.Text);
     }
     catch { return true; }
     return false;
 }
Exemplo n.º 3
0
 private void m_TxtLocator_TextChanged(object sender, EventArgs e)
 {
     Locator l = new Locator(m_TxtLocator.Text);
     if (!l.Equals(Locator.Unknown))
         SourceLocator = l;
 }
Exemplo n.º 4
0
        private void PopulatePreviousContactsGridCallback(List<Contact> contacts)
        {
            Locator ourLocation = m_OurLocatorValue;
            //m_QSOGrid.Rows.Clear();
            string station = m_Station.Text;
            string band = m_Band.Text;
            string op = m_OurOperator.Text;

            for (int i = 1; i < m_ContactTable.RowCount - 1; i++)
            {
                int contactsIndex = m_ContactTable.RowCount - i-2;
                if (contacts.Count > contactsIndex)
                {
                    Contact c = contacts[contactsIndex];
                    bool alert = (c.Notes.Contains(station) || c.Notes.Contains(band) || c.Notes.Contains(op));
                    Label[] rowLabels = m_ContactTableLabels[i - 1];
                    rowLabels[(int)ContactTableColumns.Band].Text = BandTextFromContact(c);

                    Locator theirLocator;
                    if (c.LocatorReceived != null && !(c.LocatorReceived.Latitude == 0 && c.LocatorReceived.Longitude == 0))
                    {
                        theirLocator = c.LocatorReceived;
                    }
                    else
                    {
                        PrefixRecord pr = m_CallsignLookup.LookupPrefix(c.Callsign);
                        if (pr != null)
                            theirLocator = new Locator(pr.Latitude, pr.Longitude);
                        else
                            theirLocator = new Locator(0, 0);
                    }
                    rowLabels[(int)ContactTableColumns.Beam].Text = Geographics.BeamHeading(ourLocation, theirLocator).ToString().PadLeft(3, '0');
                    rowLabels[(int)ContactTableColumns.Distance].Text = ((int)Math.Ceiling(Geographics.GeodesicDistance(ourLocation, theirLocator) / 1000)).ToString();

                    rowLabels[(int)ContactTableColumns.Callsign].Text = c.Callsign;

                    rowLabels[(int)ContactTableColumns.Comments].Text = c.Notes;
                    if (alert)
                        rowLabels[(int)ContactTableColumns.Comments].BackColor = Color.Pink;
                    else
                        rowLabels[(int)ContactTableColumns.Comments].BackColor = SystemColors.Control;

                    rowLabels[(int)ContactTableColumns.LocatorReceived].Text = c.LocatorReceivedString;
                    rowLabels[(int)ContactTableColumns.RstReceived].Text = c.ReportReceived;
                    rowLabels[(int)ContactTableColumns.RstSent].Text = c.ReportSent;
                    rowLabels[(int)ContactTableColumns.SerialReceived].Text = c.SerialReceived.ToString().PadLeft(3, '0');
                    rowLabels[(int)ContactTableColumns.SerialSent].Text = c.SerialSent.ToString().PadLeft(3, '0');
                    rowLabels[(int)ContactTableColumns.Time].Text = c.StartTime.ToString("HHmm");
                    m_ContactIds[i - 1] = new KeyValuePair<int, int>(c.SourceId, c.Id);

                    if (alert)
                    {
                        Array.ForEach(rowLabels, l => { l.ForeColor = Color.Black; l.BackColor = Color.Pink; });
                    }
                    else if (string.Equals(c.Station, m_Station.Text, StringComparison.InvariantCultureIgnoreCase))
                    {
                        Array.ForEach(rowLabels, l => { l.ForeColor = Color.Black; l.BackColor = SystemColors.Control; });
                    }
                    else
                    {
                        Array.ForEach(rowLabels, l => { l.ForeColor = Color.DarkGray; l.BackColor = SystemColors.Control; });
                    }

                    //DataGridViewRow row = new DataGridViewRow();
                    //m_QSOGrid.Rows.Add(row);
                    //row.Cells[(int)ContactTableColumns.Beam].Value = Geographics.BeamHeading(ourLocation, theirLocator).ToString().PadLeft(3, '0');
                    //row.Cells[(int)ContactTableColumns.Distance].Value = ((int)Math.Ceiling(Geographics.GeodesicDistance(ourLocation, theirLocator) / 1000)).ToString();

                    //row.Cells[(int)ContactTableColumns.Callsign].Value = c.Callsign;
                    //row.Cells[(int)ContactTableColumns.Comments].Value = c.Notes;
                    //row.Cells[(int)ContactTableColumns.LocatorReceived].Value = c.LocatorReceivedString;
                    //row.Cells[(int)ContactTableColumns.RstReceived].Value = c.ReportReceived;
                    //row.Cells[(int)ContactTableColumns.RstSent].Value = c.ReportSent;
                    //row.Cells[(int)ContactTableColumns.SerialReceived].Value = c.SerialReceived.ToString().PadLeft(3, '0');
                    //row.Cells[(int)ContactTableColumns.SerialSent].Value = c.SerialSent.ToString().PadLeft(3, '0');
                    //row.Cells[(int)ContactTableColumns.Time].Value = c.StartTime.ToString("HHmm");
                }
                else
                {
                    foreach (Label l in m_ContactTableLabels[i - 1])
                        l.Text = string.Empty;
                }
            }
        }
Exemplo n.º 5
0
        private void m_OurLocator_TextChanged(object sender, EventArgs e)
        {
            if (m_OurLocator.TextLength == 6)
            {
                try
                {
                    m_OurLocatorValue = new Locator(m_OurLocator.Text);

                    Settings.Set("Locator", m_OurLocator.Text);
                }
                catch (ArgumentException)
                {
                }
            }
        }
Exemplo n.º 6
0
        private void m_Locator_TextChanged(object sender, EventArgs e)
        {
            if (m_Locator.TextLength == 6)
            {
                try
                {
                    Locator theirLocator = new Locator(m_Locator.Text);
                    m_Beam.Text = Geographics.BeamHeading(m_OurLocatorValue, theirLocator).ToString();
                    int distance = (int)Math.Ceiling(Geographics.GeodesicDistance(m_OurLocatorValue, theirLocator) / 1000);
                    if (distance == 0)
                        distance = 1; // By definition - QSOs in same square = 1 point
                    m_Distance.Text = distance.ToString();
                }
                catch (ArgumentException)
                {
                    m_Beam.Text = m_Distance.Text = string.Empty;
                }
            }
            else
            {
                m_Beam.Text = m_Distance.Text = string.Empty;
            }

            if (m_Locator.TextLength >= 4)
            {
                bool isNewSquare = m_ContactStore.IsNewSquare(m_Locator.Text, BandHelper.Parse(m_Band.Text));
                if (isNewSquare)
                    m_Locator.BackColor = Color.LightGreen;
                else
                    m_Locator.BackColor = SystemColors.Window;
            }
            else
            {
                m_Locator.BackColor = SystemColors.Window;
            }
        }
Exemplo n.º 7
0
        private void CallsignChangedWorker(string callsign, string ourBandText, Locator ourLocatorValue)
        {
            try
            {
                string notesText = null;
                Color notesBackColor = Color.Transparent;
                string locatorText = null;
                string beamText = null, distanceText = null, commentsText = null;
                object[] matchesKnownCalls = null, matchesThisContest = null, locatorMatchesThisContest = null;

                Locator existingLocator;
                if (callsign.Length > 2)
                {
                    List<Band> bands = m_ContactStore.GetPreviousBands(callsign, out existingLocator);
                    Band ourBand = BandHelper.Parse(ourBandText);
                    if (bands.Contains(ourBand))
                    {
                        notesBackColor = c_DupeColor;
                        List<Contact> previousQsos = m_ContactStore.GetPreviousContacts(callsign);
                        if (previousQsos.Count > 0)
                        {
                            Contact previousQso = previousQsos[previousQsos.Count - 1];
                            notesText = string.Format("Already worked {0} on {1} (TX: {2} {3:000} / RX: {4} {5:000} on {6} / {7})", callsign, BandHelper.ToString(ourBand), previousQso.ReportSent, previousQso.SerialSent, previousQso.ReportReceived, previousQso.SerialReceived, previousQso.StartTime.ToString("d MMM HH:mm"), previousQso.LocatorReceivedString);
                        }
                        else
                        {
                            notesText = string.Format("Already worked {0} on {1} (Missing QSO details?)", callsign, BandHelper.ToString(ourBand));
                        }
                    }
                    else if (bands.Count > 0)
                    {
                        notesBackColor = c_WorkedOtherBandsColor;
                        string bandString = string.Empty;
                        foreach (Band b in bands)
                        {
                            bandString += BandHelper.ToString(b) + ", ";
                        }
                        notesText = string.Format("Worked {0} on {1} - {2}", callsign, bandString, existingLocator);
                        if (existingLocator != null)
                            locatorText = existingLocator.ToString();
                    }
                    else
                    {
                        notesText = string.Empty;
                        notesBackColor = Color.Transparent;
                    }

                    // Do a first guess beam heading etc
                    PrefixRecord pfx = m_CallsignLookup.LookupPrefix(callsign);
                    if (pfx != null)
                    {
                        Locator theirLocator = new Locator(pfx.Latitude, pfx.Longitude);

                        // Only use the DXCC if it's a substantial distance (500km) away from us
                        if (Geographics.GeodesicDistance(ourLocatorValue, theirLocator) > 500 * 1000)
                        {
                            beamText = Geographics.BeamHeading(ourLocatorValue, theirLocator).ToString();
                            distanceText = Math.Ceiling(Geographics.GeodesicDistance(ourLocatorValue, theirLocator) / 1000).ToString();
                        }

                        // Only change comments field if it has not been manually changed
                        if ((m_Comments.Text == m_lastComment) || (m_Comments.Text == ""))
                        {
                            m_lastComment = commentsText = pfx.Entity;
                        }
                    }
                }

                // Populate the lists of partial callsign matches
                if (callsign.Length > 0)
                {
                    matchesKnownCalls = m_ContactStore.GetPartialMatchesKnownCalls(callsign).ToArray();
                    matchesThisContest = m_ContactStore.GetPartialMatchesThisContest(callsign).ToArray();
                }

                // Also search locators if we've got enough digits for it to be sensibly
                // distinguished from a callsign
                if (callsign.Length > 2)
                {
                    locatorMatchesThisContest = m_ContactStore.GetLocatorMatchesThisContest(callsign).ToArray();
                }

                // Actually populate everything back on the UI thread!
                if (Disposing || IsDisposed || !IsHandleCreated)
                    return;
                Invoke(new MethodInvoker(() =>
                {
                    // If the callsign has been changed while we've been doing this work, don't do the update - another request
                    // will have been kicked off since, and we don't want to trample on the later update
                    if (m_Callsign.Text != callsign)
                        return;

                    if (!string.IsNullOrWhiteSpace(notesText))
                    {
                        m_Notes.Text = notesText;
                        m_Notes.BackColor = notesBackColor;
                    }

                    if (locatorText != null) m_Locator.Text = locatorText;

                    m_Beam.Text = beamText ?? string.Empty;
                    m_Distance.Text = distanceText ?? string.Empty;
                    if (commentsText != null)
                    {
                        m_Comments.Text = commentsText;
                    }

                    if (matchesKnownCalls != null || locatorMatchesThisContest != null)
                    {
                        m_MatchesKnownCalls.BeginUpdate();
                        m_MatchesKnownCalls.Items.Clear();

                        if (matchesKnownCalls != null)
                            m_MatchesKnownCalls.Items.AddRange(matchesKnownCalls);
                        if (locatorMatchesThisContest != null)
                            m_MatchesKnownCalls.Items.AddRange(locatorMatchesThisContest);
                        m_MatchesKnownCalls.EndUpdate();
                    }
                    if (matchesThisContest != null)
                    {
                        m_MatchesThisContest.BeginUpdate();
                        m_MatchesThisContest.Items.Clear();
                        m_MatchesThisContest.Items.AddRange(matchesThisContest);
                        m_MatchesThisContest.EndUpdate();
                    }
                }));
            }
            catch (Exception ex)
            {
            }
        }
Exemplo n.º 8
0
        //private string GetContactLog(Contact c)
        //{
        // string thisEntry = string.Format("{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14} {15} {16} {17} {18}",
        // c.Time.ToString("yyMMdd"),
        // c.Time.ToString ("HHmm"),
        // BandHelper.ToMHzString (c.Band).PadRight(4),
        // ModeHelper.ToOfficialString(c.Mode).PadRight(3),
        // c.Callsign.PadRight (15),
        // c.ReportSent.PadRight (3),
        // c.SerialSent.ToString().PadRight (4),
        // c.ReportReceived.PadRight (3),
        // c.SerialReceived.ToString().PadRight (4),
        // string.Empty.PadRight (4) /* bonus multiplier etc */,
        // c.Points.ToString().PadRight (4),
        // c.Operator.PadRight(6),
        // c.LocatorReceived.ToString(),
        // string.Empty.PadRight (1) /* locator multiplier */,
        // string.Empty.PadRight (3) /* postcode */,
        // string.Empty.PadRight(1) /* postcode mult */,
        // string.Empty.PadRight (3) /* country code */,
        // string.Empty.PadRight (1) /* country code mult */,
        // c.Notes + "<CE>"
        // );
        // return thisEntry;
        //}
        private string GetContactLog(Contact c, Locator sourceLocator, List<string> locator4SquaresSeen, List<string> ukLocator4SquaresSeen, out int points)
        {
            // Figure out if this QSO is valid as a mult
            bool qualifiesForMult;
            bool qualifiesForUkMult;
            PrefixRecord prefix = m_CallsignLookup.LookupPrefix(c.Callsign.Trim());
            if (prefix == null || prefix.Entity == null)
            {
                qualifiesForMult = false;
                qualifiesForUkMult = false;
            }
            else
            {
                switch (prefix.Entity)
                {
                    case "ENGLAND":
                    case "NORTHERN IRELAND":
                    case "WALES":
                    case "SCOTLAND":
                    case "ISLE OF MAN":
                    case "JERSEY":
                    case "GUERNSEY":
                        qualifiesForMult = true;
                        qualifiesForUkMult = true;
                        break;
                    default:
                        qualifiesForMult = true;
                        qualifiesForUkMult = false;
                        break;
                }
            }

            string square4;
            if (c.LocatorReceivedString != null && c.LocatorReceivedString.Length >= 4)
                square4 = c.LocatorReceivedString.Substring(0, 4);
            else
                square4 = string.Empty;

            bool newUkSquare = qualifiesForUkMult && !ukLocator4SquaresSeen.Contains(square4.ToLowerInvariant());
            if (newUkSquare)
                ukLocator4SquaresSeen.Add(square4.ToLowerInvariant());

            bool newSquare = qualifiesForMult && !locator4SquaresSeen.Contains(square4.ToLowerInvariant());
            if (newSquare)
                locator4SquaresSeen.Add(square4.ToLowerInvariant());

            string thisEntry = string.Format("{0};{1};{2};{3};{4};{5};{6};{7};{8};{9};{10};{11};{12};{13};{14}",
                c.EndTime.ToString("yyMMdd"),
                c.EndTime.ToString("HHmm"),
                c.Callsign,
                string.Empty /* mode code */,
                c.ReportSent,
                c.SerialSent,
                c.ReportReceived,
                c.SerialReceived,
                string.Empty /* received exchange */,
                c.LocatorReceivedString,
                points = (int)Math.Ceiling(Geographics.GeodesicDistance(sourceLocator, c.LocatorReceived) / 1000),
                string.Empty /* new exchange */,
                newSquare ? "N" : string.Empty /* new locator square */,
                string.Empty /* new DXCC */,
                string.Empty /* dupe */);
            return thisEntry;
        }
Exemplo n.º 9
0
 public List<Band> GetPreviousBands(string callsign, out Locator locator)
 {
     List<Contact> previousContacts = GetPreviousContacts(callsign);
     List<Band> previousBands = new List<Band>();
     locator = null;
     foreach (Contact c in previousContacts)
     {
         if (c.Band != Band.Unknown && !previousBands.Contains(c.Band))
             previousBands.Add(c.Band);
         locator = c.LocatorReceived;
     }
     return previousBands;
 }
Exemplo n.º 10
0
        public string ExportLog(Locator sourceLocator, Band band)
        {
            MySqlConnection conn = OpenConnection;
            lock (conn)
            {
                List<KeyValuePair<int, int>> contactIDs = new List<KeyValuePair<int, int>>();
                using (MySqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT sourceId, id FROM log WHERE band=?band ORDER BY startTime";
                    cmd.Parameters.AddWithValue("?band", BandHelper.ToString(band));
                    using (MySqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                            contactIDs.Add(new KeyValuePair<int, int>(reader.GetInt32(0), reader.GetInt32(1)));
                    }
                }

                StringBuilder sb = new StringBuilder();
                List<string> locator4SquaresSeen = new List<string> ();
                List<string> ukLocator4SquaresSeen = new List<string>();
                int totalPoints = 0;
                int oDxPoints = 0;
                Contact oDxContact = null;
                foreach (KeyValuePair<int, int> id in contactIDs)
                {
                    int pts;
                    Contact c = LoadContact(id.Key, id.Value);
                    sb.AppendLine(GetContactLog(c, sourceLocator, locator4SquaresSeen, ukLocator4SquaresSeen, out pts));
                    totalPoints += pts;
                    if (pts > oDxPoints)
                    {
                        oDxContact = c;
                        oDxPoints = pts;
                    }
                }

                // Write the header, now we have the various info
                Reg1TestHeader header = new Reg1TestHeader
                {
                    Antenna = "ANTENNA",
                    Band = band,
                    Callsign = "G3PYE/P",
                    Club = "Camb-Hams",
                    ContactAddress1 = "13 Harlestones Road",
                    ContactAddress2 = "Cottenham",
                    ContactCall = "M0VFC",
                    ContactCity = "Cambridge",
                    ContactCounty = string.Empty,
                    ContactEmail = "*****@*****.**",
                    ContactName = "Robert Chipperfield",
                    ContactPhone = "07990 646923",
                    ContactPostCode = "CB24 8TR",
                    ContestName = "UKAC",
                    EndDate = new DateTime(2010, 07, 04),
                    HeightAboveGround = 20,
                    HeightAboveSea = 68,
                    Locator = new Locator("JO02CE"),
                    Multipliers = locator4SquaresSeen.Count + ukLocator4SquaresSeen.Count,
                    OdxCall = oDxContact.Callsign,
                    OdxLocator = oDxContact.LocatorReceived,
                    OdxDistance = oDxPoints,
                    Operators = "M0LCM,M0VFC,M0ZRN,M1BXF",
                    Points = totalPoints,
                    Power = 100,
                    Qsos = contactIDs.Count,
                    Receiver = "RECEIVER",
                    Section = "UKAC Restricted",
                    StartDate = new DateTime(2010, 07, 03),
                    TotalScore = totalPoints * (locator4SquaresSeen.Count + ukLocator4SquaresSeen.Count),
                    Transmitter = "TRANSMITTER"
                };

                sb.Insert(0, header.HeaderText);
                return sb.ToString();
            }
        }
Exemplo n.º 11
0
 private void m_Locator_TextChanged(object sender, EventArgs e)
 {
     try
     {
         Locator theirLocator = new Locator(m_Locator.Text);
         m_Beam.Text = Geographics.BeamHeading(OurLocator, theirLocator).ToString();
         int distance = (int)Math.Ceiling(Geographics.GeodesicDistance(OurLocator, theirLocator) / 1000);
         if (distance == 0)
             distance = 1; // By definition - QSOs in same square = 1 point
         m_Distance.Text = distance.ToString();
     }
     catch (ArgumentException)
     {
         m_Beam.Text = m_Distance.Text = string.Empty;
     }
 }
Exemplo n.º 12
0
        public static double GeodesicDistance(Locator start, Locator end)
        {
            if (start == null || end == null)
                return 0;

            double L = DegreesToRadians(end.Longitude - start.Longitude);

            //var U1 = Math.atan((1-f) * Math.tan(lat1.toRad()));
            //var U2 = Math.atan((1-f) * Math.tan(lat2.toRad()));
            //var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
            //var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
            double U1 = Math.Atan((1 - c_Wgs84_F) * Math.Tan(DegreesToRadians(start.Latitude)));
            double U2 = Math.Atan((1 - c_Wgs84_F) * Math.Tan(DegreesToRadians(end.Latitude)));
            double sinU1 = Math.Sin(U1);
            double cosU1 = Math.Cos(U1);
            double sinU2 = Math.Sin(U2);
            double cosU2 = Math.Cos(U2);

            //var lambda = L, lambdaP, iterLimit = 100;
            //  do {
            //    var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
            //    var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
            //      (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
            //    if (sinSigma==0) return 0;  // co-incident points
            //    var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
            //    var sigma = Math.atan2(sinSigma, cosSigma);
            //    var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
            //    var cosSqAlpha = 1 - sinAlpha*sinAlpha;
            //    var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
            //    if (isNaN(cos2SigmaM)) cos2SigmaM = 0;  // equatorial line: cosSqAlpha=0 (§6)
            //    var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
            //    lambdaP = lambda;
            //    lambda = L + (1-C) * f * sinAlpha *
            //      (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
            //  } while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0);
            double lambda = L;
            double lambdaP;
            double cosSqAlpha, sinSigma, cos2SigmaM, cosSigma, sigma;
            int iterLimit = 100;
            do
            {
                double sinLambda = Math.Sin(lambda);
                double cosLambda = Math.Cos(lambda);
                sinSigma = Math.Sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) +
                    (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
                if (sinSigma == 0)
                    return 0; // co-incident points
                cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
                sigma = Math.Atan2(sinSigma, cosSigma);
                double sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
                cosSqAlpha = 1 - sinAlpha * sinAlpha;
                cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
                if (double.IsNaN(cos2SigmaM))
                    cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6)
                double C = c_Wgs84_F / 16 * cosSqAlpha * (4 + c_Wgs84_F * (4 - 3 * cosSqAlpha));
                lambdaP = lambda;
                lambda = L + (1 - C) * c_Wgs84_F * sinAlpha *
                    (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
            }
            while (Math.Abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);

            if (iterLimit == 0) return double.NaN;  // formula failed to converge

            double uSq = cosSqAlpha * (c_Wgs84_A * c_Wgs84_A - c_Wgs84_B * c_Wgs84_B) / (c_Wgs84_B * c_Wgs84_B);
            double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
            double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
            double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
              B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
            double s = c_Wgs84_B * A * (sigma - deltaSigma);

            s = Math.Round(s, 3); // round to 1mm precision
            return s;
        }