/// <summary> /// The great circle distance in radians between two spherical coordinates. /// This function uses the Haversine formula. /// For math details, see: /// https://en.wikipedia.org/wiki/Haversine_formula /// https://www.movable-type.co.uk/scripts/latlong.html /// </summary> /// <param name="a">the first lat/lng pair (in radians)</param> /// <param name="b">the second lat/lng pair (in radians)</param> /// <returns> /// the great circle distance in radians between a and b /// </returns> /// <!-- /// geoCoord.c /// double H3_EXPORT(pointDistRads) /// --> public static decimal DistanceToRadians(this GeoCoord a, GeoCoord b) { decimal sinLat = DecimalEx.Sin((b.Latitude - a.Latitude) / 2.0m); decimal sinLng = DecimalEx.Sin((b.Longitude - a.Longitude) / 2.0m); decimal p = sinLat * sinLat + DecimalEx.Cos(a.Latitude) * DecimalEx.Cos(b.Latitude) * sinLng * sinLng; return(2 * DecimalEx.ATan2(DecimalEx.Sqrt(p), DecimalEx.Sqrt(1 - p))); }
/// <summary> /// Determines the azimuth to p2 from p1 in radians /// </summary> /// <param name="p1">The first spherical coordinates</param> /// <param name="p2">The second spherical coordinates</param> /// <returns>The azimuth in radians from p1 to p2</returns> /// <!-- /// geoCoord.c /// double _geoAzimuthRads /// --> internal static decimal AzimuthRadiansTo(this GeoCoord p1, GeoCoord p2) { return (DecimalEx.ATan2 ( DecimalEx.Cos(p2.Latitude) * DecimalEx.Sin(p2.Longitude - p1.Longitude), DecimalEx.Cos(p1.Latitude) * DecimalEx.Sin(p2.Latitude) - DecimalEx.Sin(p1.Latitude) * DecimalEx.Cos(p2.Latitude) * DecimalEx.Cos(p2.Longitude - p1.Longitude) )); }
/// <summary> /// Determines the center point in spherical coordinates of a cell given by 2D /// hex coordinates on a particular icosahedral face. /// </summary> /// <param name="v">The 2D hex coordinates of the cell</param> /// <param name="face">The icosahedral face upon which the 2D hex coordinate system is centered</param> /// <param name="res">The H3 resolution of the cell</param> /// <param name="substrate"> /// Indicates whether or not this grid is actually a substrate /// grid relative to the specified resolution. /// </param> /// <returns>The spherical coordinates of the cell center point</returns> /// <!-- /// faceIjk.c /// void _hex2dToGeo /// --> public static GeoCoord ToGeoCoord(this Vec2d v, int face, int res, int substrate) { // calculate (r, theta) in hex2d decimal r = v.Magnitude; bool bSubstrate = substrate != 0; if (r < Constants.H3.EPSILON) { return(Constants.FaceIjk.FaceCenterGeo[face]); } decimal theta = DecimalEx.ATan2(v.Y, v.X); // scale for current resolution length u for (var i = 0; i < res; i++) { r /= Constants.FaceIjk.MSqrt7; } // scale accordingly if this is a substrate grid if (substrate != 0) { r /= 3.0m; if (res.IsResClassIii()) { r /= Constants.FaceIjk.MSqrt7; } } r *= Constants.H3.RES0_U_GNOMONIC; // perform inverse gnomonic scaling of r r = DecimalEx.ATan(r); // adjust theta for Class III // if a substrate grid, then it's already been adjusted for Class III if (!bSubstrate && res.IsResClassIii()) { theta = (theta + Constants.H3.M_AP7_ROT_RADS).NormalizeRadians(); } // find theta as an azimuth theta = (Constants.FaceIjk.FaceAxesAzRadsCii[face, 0] - theta).NormalizeRadians(); // now find the point at (r,theta) from the face center return(Constants.FaceIjk.FaceCenterGeo[face] .GetAzimuthDistancePoint(theta, r)); }
public void Test(decimal y, decimal x, decimal expected, decimal tolerance) { tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); Assert.That(DecimalEx.ATan2(y, x), Is.EqualTo(expected).Within(tolerance)); }
/// <summary> /// Computes the point on the sphere a specified azimuth and distance from /// another point. /// </summary> /// <param name="p1">The first spherical coordinates.</param> /// <param name="azimuth">The desired azimuth from p1.</param> /// <param name="distance">The desired distance from p1, must be non-negative.</param> /// <returns>The spherical coordinates at the desired azimuth and distance from p1.</returns> /// <!-- /// geoCoord.c /// void _geoAzDistanceRads /// --> internal static GeoCoord GetAzimuthDistancePoint(this GeoCoord p1, decimal azimuth, decimal distance) { if (distance < Constants.H3.EPSILON) { return(p1); } azimuth = azimuth.NormalizeRadians().ConstrainToPiAccuracy(); var p2 = new GeoCoord(); // check for due north/south azimuth if (azimuth < Constants.H3.EPSILON || Math.Abs(azimuth - Constants.H3.M_PI) < Constants.H3.EPSILON) { if (azimuth < Constants.H3.EPSILON) // due north { p2 = p2.SetLatitude(p1.Latitude + distance); } else // due south { p2 = p2.SetLatitude(p1.Latitude - distance); } if (Math.Abs(p2.Latitude - Constants.H3.M_PI_2) < Constants.H3.EPSILON) // north pole { p2 = new GeoCoord(Constants.H3.M_PI_2, 0.0m); } else if (Math.Abs(p2.Latitude + Constants.H3.M_PI_2) < Constants.H3.EPSILON) // south pole { p2 = new GeoCoord(-Constants.H3.M_PI_2, 0.0m); } else { p2 = p2.SetLongitude(p1.Longitude.ConstrainLongitude()); } } else // Not due north or south { decimal sinLatitude = DecimalEx.Sin(p1.Latitude) * DecimalEx.Cos(distance) + DecimalEx.Cos(p1.Latitude) * DecimalEx.Sin(distance) * DecimalEx.Cos(azimuth); sinLatitude = sinLatitude.ConstrainToPiAccuracy(); if (sinLatitude > 1.0m) { sinLatitude = 1.0m; } if (sinLatitude < -1.0m) { sinLatitude = 1.0m; } p2 = p2.SetLatitude(DecimalEx.ASin(sinLatitude).ConstrainToPiAccuracy()); if (Math.Abs(p2.Latitude - Constants.H3.M_PI_2) < Constants.H3.EPSILON) // north pole { p2 = new GeoCoord(Constants.H3.M_PI_2, 0.0m); } else if (Math.Abs(p2.Latitude + Constants.H3.M_PI_2) < Constants.H3.EPSILON) // south pole { p2 = new GeoCoord(-Constants.H3.M_PI_2, 0.0m); } else { decimal sinLongitude = DecimalEx.Sin(azimuth) * DecimalEx.Sin(distance) / DecimalEx.Cos(p2.Latitude); decimal cosLongitude = (DecimalEx.Cos(distance) - DecimalEx.Sin(p1.Latitude) * DecimalEx.Sin(p2.Latitude)) / DecimalEx.Cos(p1.Latitude) / DecimalEx.Cos(p2.Latitude); if (sinLongitude > 1.0m) { sinLongitude = 1.0m; } if (sinLongitude < -1.0m) { sinLongitude = -1.0m; } if (cosLongitude > 1.0m) { cosLongitude = 1.0m; } if (cosLongitude < -1.0m) { cosLongitude = -1.0m; } p2 = p2.SetLongitude ( (p1.Longitude + DecimalEx.ATan2(sinLongitude, cosLongitude)) .ConstrainLongitude().ConstrainToPiAccuracy() ); } } return(p2); }