/// <summary>
        /// Converts ‘this’ (geocentric) cartesian (x/y/z) point to (ellipsoidal geodetic) latitude/longitude coordinates on specified datum.
        /// Uses Bowring’s (1985) formulation for μm precision in concise form.
        /// </summary>
        /// <param name="toDatum">Datum to use when converting point.</param>
        public LatLonEllipsoidal ToLatLonE(Datum toDatum)
        {
            var Major      = toDatum.Ellipsoid.Major;
            var Minor      = toDatum.Ellipsoid.Minor;
            var Flattening = toDatum.Ellipsoid.Flattening;

            var E2 = 2 * Flattening - Flattening * Flattening; // 1st eccentricity squared ≡ (a²-b²)/a²
            var ε2 = E2 / (1 - E2);                            // 2nd eccentricity squared ≡ (a²-b²)/b²
            var P  = Math.Sqrt(X * X + Y * Y);                 // distance from minor axis
            var R  = Math.Sqrt(P * P + Z * Z);                 // polar radius

            // parametric latitude (Bowring eqn 17, replacing tanβ = z·a / p·b)
            var Tanβ = (Minor * Z) / (Major * P) * (1 + ε2 * Minor / R);
            var Sinβ = Tanβ / Math.Sqrt(1 + Tanβ * Tanβ);
            var Cosβ = Sinβ / Tanβ;

            // geodetic latitude (Bowring eqn 18: tanφ = z+ε²bsin³β / p−e²cos³β)
            var φ = Math.Atan2(Z + ε2 * Minor * Sinβ * Sinβ * Sinβ, P - E2 * Major * Cosβ * Cosβ * Cosβ);

            // longitude
            var λ = Math.Atan2(Y, X);

            // height above ellipsoid (Bowring eqn 7) [not currently used]
            var Sinφ = Math.Sin(φ);
            var Cosφ = Math.Cos(φ);
            var V    = Major / Math.Sqrt(1 - E2 * Sinφ * Sinφ);          // length of the normal terminated by the minor axis
            var H    = P * Cosφ + Z * Sinφ - (Major * Major / V);

            var point = new LatLonEllipsoidal(φ.ToDegrees(), λ.ToDegrees(), toDatum);

            return(point);
        }
        /// <summary>
        /// /Converts ‘this’ lat/lon coordinate to new coordinate system.
        /// </summary>
        /// <param name="toDatum">Datum this coordinate is to be converted to.</param>
        /// <example>
        /// var pWGS84 = new LatLon(51.4778, -0.0016, Datum.WGS84);
        /// var pOSGB = pWGS84.convertDatum(Datum.OSGB36); // 51.4773°N, 000.0000°E
        /// </example>
        public LatLonEllipsoidal ConvertDatum(Datum toDatum)
        {
            LatLonEllipsoidal OldLatLon = this;

            double[] Transform  = toDatum.Transform;
            bool     UsingWgs84 = false;

            if (OldLatLon.Datum == Datum.WGS84)
            {
                // converting from WGS 84
                Transform  = toDatum.Transform;
                UsingWgs84 = true;
            }
            if (toDatum == Datum.WGS84)
            {
                // converting to WGS 84; use inverse transform (don't overwrite original!)
                UsingWgs84 = true;
                Transform  = new double[7];
                for (var p = 0; p < 7; p++)
                {
                    Transform[p] = -OldLatLon.Datum.Transform[p];
                }
            }
            if (!UsingWgs84)
            {
                // neither this.datum nor toDatum are WGS84: convert this to WGS84 first
                OldLatLon = ConvertDatum(Datum.WGS84);
            }

            var OldCartesian = OldLatLon.ToCartesian();                            // convert polar to cartesian...
            var NewCartesian = OldCartesian.ApplyTransform(Transform);             // ...apply transform...
            var NewLatLon    = NewCartesian.ToLatLonE(toDatum);                    // ...and convert cartesian to polar

            return(NewLatLon);
        }
        /// <summary>Converts Ordnance Survey grid reference easting/northing coordinate to latitude/longitude (SW corner of grid square).
        ///
        /// Note formulation implemented here due to Thomas, Redfearn, etc is as published by OS, but is
        /// inferior to Krüger as used by e.g. Karney 2011.</summary>
        ///<param name="gridRef">Grid ref E/N to be converted to lat/long (SW corner of grid square).</param>
        ///<param name="datum">Datum to convert grid reference into.</param>
        ///<example>var gridref = new OsGridRef(651409.903, 313177.270);
        /// var pWgs84 = OsGridRef.osGridToLatLon(gridref);                     // 52°39′28.723″N, 001°42′57.787″E
        /// to obtain (historical) OSGB36 latitude/longitude point:
        /// var pOsgb = OsGridRef.osGridToLatLon(gridref, LatLon.datum.OSGB36); // 52°39′27.253″N, 001°43′04.518″E
        ///</example>
        ///<remarks>Currently not accurate enough, owing to a lack of precision in C#'s number handling. Will be ported to use arbitrary-precision maths.</remarks>
        public LatLonEllipsoidal ToLatLon(Datum datum)
        {
            var E = Easting;
            var N = Northing;

            var a  = 6377563.396;
            var b  = 6356256.909;                     // Airy 1830 major & minor semi-axes
            var F0 = 0.9996012717;                    // NatGrid scale factor on central meridian
            var φ0 = (49.0).ToRadians();
            var λ0 = (-2.0).ToRadians();              // NatGrid true origin is 49°N,2°W
            var N0 = -100000;
            var E0 = 400000;                          // northing & easting of true origin, metres
            var e2 = 1 - b * b / (a * a);             // eccentricity squared
            var n  = (a - b) / (a + b);
            var n2 = n * n;
            var n3 = n * n * n;                     // n, n², n³

            var φ = φ0;
            var M = 0.0;

            do
            {
                φ = (N - N0 - M) / (a * F0) + φ;

                var Ma = (1 + n + 1.25 * n2 + 1.25 * n3) * (φ - φ0);
                var Mb = (3 * n + 3 * n * n + 2.625 * n3) * Math.Sin(φ - φ0) * Math.Cos(φ + φ0);
                var Mc = (1.875 * n2 + 1.875 * n3) * Math.Sin(2 * (φ - φ0)) * Math.Cos(2 * (φ + φ0));
                var Md = (35.0 / 24.0) * n3 * Math.Sin(3 * (φ - φ0)) * Math.Cos(3 * (φ + φ0));
                M = b * F0 * (Ma - Mb + Mc - Md);         // meridional arc
            } while (N - N0 - M >= 0.00001);              // ie until < 0.01mm

            var cosφ = Math.Cos(φ);
            var sinφ = Math.Sin(φ);
            var ν    = a * F0 / Math.Sqrt(1 - e2 * sinφ * sinφ);                // nu = transverse radius of curvature
            var ρ    = a * F0 * (1 - e2) / Math.Pow(1 - e2 * sinφ * sinφ, 1.5); // rho = meridional radius of curvature
            var η2   = ν / ρ - 1;                                               // eta = ?

            var tanφ  = Math.Tan(φ);
            var tan2φ = tanφ * tanφ;
            var tan4φ = tan2φ * tan2φ;
            var tan6φ = tan4φ * tan2φ;
            var secφ  = 1 / cosφ;
            var ν3    = ν * ν * ν;
            var ν5    = ν3 * ν * ν;
            var ν7    = ν5 * ν * ν;
            var VII   = tanφ / (2 * ρ * ν);
            var VIII  = tanφ / (24 * ρ * ν3) * (5 + 3 * tan2φ + η2 - 9 * tan2φ * η2);
            var IX    = tanφ / (720 * ρ * ν5) * (61 + 90 * tan2φ + 45 * tan4φ);
            var X     = secφ / ν;
            var XI    = secφ / (6 * ν3) * (ν / ρ + 2 * tan2φ);
            var XII   = secφ / (120 * ν5) * (5 + 28 * tan2φ + 24 * tan4φ);
            var XIIA  = secφ / (5040 * ν7) * (61 + 662 * tan2φ + 1320 * tan4φ + 720 * tan6φ);

            var dE  = (E - E0);
            var dE2 = dE * dE;
            var dE3 = dE2 * dE;
            var dE4 = dE2 * dE2;
            var dE5 = dE3 * dE2;
            var dE6 = dE4 * dE2;
            var dE7 = dE5 * dE2;

            φ = φ - VII * dE2 + VIII * dE4 - IX * dE6;
            var λ = λ0 + X * dE - XI * dE3 + XII * dE5 - XIIA * dE7;

            var Point = new LatLonEllipsoidal(φ.ToDegrees(), λ.ToDegrees(), Datum.OSGB36);

            if (datum != Datum.OSGB36)
            {
                Point = Point.ConvertDatum(datum);
            }

            return(Point);
        }
        public OsGridRef ToGridRef()
        {
            // if necessary convert to OSGB36 first
            if (Datum != Datum.OSGB36)
            {
                var Point = new LatLonEllipsoidal(Latitude, Longitude, Datum);
                Point     = Point.ConvertDatum(Datum.OSGB36);
                Latitude  = Point.Latitude;
                Longitude = Point.Longitude;
                Datum     = Datum.OSGB36;
            }

            var φ = Latitude.ToRadians();
            var λ = Longitude.ToRadians();

            var A  = 6377563.396;
            var B  = 6356256.909;                     // Airy 1830 major & minor semi-axes
            var F0 = 0.9996012717;                    // NatGrid scale factor on central meridian
            var φ0 = (49.0).ToRadians();
            var λ0 = (-2.0).ToRadians();              // NatGrid true origin is 49°N,2°W
            var N0 = -100000;
            var E0 = 400000;                          // northing & easting of true origin, metres
            var E2 = 1 - (B * B) / (A * A);           // eccentricity squared
            var N  = (A - B) / (A + B);
            var N2 = N * N;
            var N3 = N * N * N;                     // n, n², n³

            var Cosφ = Math.Cos(φ);
            var Sinφ = Math.Sin(φ);
            var ν    = A * F0 / Math.Sqrt(1 - E2 * Sinφ * Sinφ);                // nu = transverse radius of curvature
            var ρ    = A * F0 * (1 - E2) / Math.Pow(1 - E2 * Sinφ * Sinφ, 1.5); // rho = meridional radius of curvature
            var η2   = ν / ρ - 1;                                               // eta = ?

            var Ma = (1 + N + 1.25 * N2 + 1.25 * N3) * (φ - φ0);
            var Mb = (3 * N + 3 * N * N + 2.625 * N3) * Math.Sin(φ - φ0) * Math.Cos(φ + φ0);
            var Mc = (1.875 * N2 + 1.875 * N3) * Math.Sin(2 * (φ - φ0)) * Math.Cos(2 * (φ + φ0));
            var Md = 35.0 / 24.0 * N3 * Math.Sin(3 * (φ - φ0)) * Math.Cos(3 * (φ + φ0));
            var M  = B * F0 * (Ma - Mb + Mc - Md);                         // meridional arc

            var Cos3φ = Cosφ * Cosφ * Cosφ;
            var Cos5φ = Cos3φ * Cosφ * Cosφ;
            var Tan2φ = Math.Tan(φ) * Math.Tan(φ);
            var Tan4φ = Tan2φ * Tan2φ;

            var I    = M + N0;
            var II   = (ν / 2) * Sinφ * Cosφ;
            var III  = (ν / 24) * Sinφ * Cos3φ * (5 - Tan2φ + 9 * η2);
            var IIIA = (ν / 720) * Sinφ * Cos5φ * (61 - 58 * Tan2φ + Tan4φ);
            var IV   = ν * Cosφ;
            var V    = (ν / 6) * Cos3φ * (ν / ρ - Tan2φ);
            var VI   = (ν / 120) * Cos5φ * (5 - 18 * Tan2φ + Tan4φ + 14 * η2 - 58 * Tan2φ * η2);

            var Δλ  = λ - λ0;
            var Δλ2 = Δλ * Δλ;
            var Δλ3 = Δλ2 * Δλ;
            var Δλ4 = Δλ3 * Δλ;
            var Δλ5 = Δλ4 * Δλ;
            var Δλ6 = Δλ5 * Δλ;

            var North = I + II * Δλ2 + III * Δλ4 + IIIA * Δλ6;
            var East  = E0 + IV * Δλ + V * Δλ3 + VI * Δλ5;

            North = Math.Round(North, 3);             //North.toFixed(3)); // round to mm precision
            East  = Math.Round(East, 3);

            return(new OsGridRef(East, North));            // gets truncated to SW corner of 1m grid square
        }