using System; using System.Diagnostics; namespace CoordinateSharp { /// /// Contains distance values between two coordinates. /// [Serializable] public class Distance { /// /// Initializes a distance object using Haversine (Spherical Earth). /// /// Coordinate 1 /// Coordinate 2 public Distance(Coordinate c1, Coordinate c2) => this.Haversine(c1, c2); /// /// Initializes a distance object using Haversine (Spherical Earth) or Vincenty (Elliptical Earth). /// /// Coordinate 1 /// Coordinate 2 /// Shape of earth public Distance(Coordinate c1, Coordinate c2, Shape shape) { if (shape == Shape.Sphere) { this.Haversine(c1, c2); } else { this.Vincenty(c1, c2); } } /// /// Initializes distance object based on distance in KM /// /// Kilometers public Distance(Double km) { this.Kilometers = km; this.Meters = km * 1000; this.Feet = this.Meters * 3.28084; this.Miles = this.Meters * 0.000621371; this.NauticalMiles = this.Meters * 0.0005399565; this.Bearing = 0;//None specified } /// /// Initializaes distance object based on specified distance and measurement type /// /// Distance /// Measurement type public Distance(Double distance, DistanceType type) { this.Bearing = 0; switch (type) { case DistanceType.Feet: this.Feet = distance; this.Meters = this.Feet * 0.3048; this.Kilometers = this.Meters / 1000; this.Miles = this.Meters * 0.000621371; this.NauticalMiles = this.Meters * 0.0005399565; break; case DistanceType.Kilometers: this.Kilometers = distance; this.Meters = this.Kilometers * 1000; this.Feet = this.Meters * 3.28084; this.Miles = this.Meters * 0.000621371; this.NauticalMiles = this.Meters * 0.0005399565; break; case DistanceType.Meters: this.Meters = distance; this.Kilometers = this.Meters / 1000; this.Feet = this.Meters * 3.28084; this.Miles = this.Meters * 0.000621371; this.NauticalMiles = this.Meters * 0.0005399565; break; case DistanceType.Miles: this.Miles = distance; this.Meters = this.Miles * 1609.344; this.Feet = this.Meters * 3.28084; this.Kilometers = this.Meters / 1000; this.NauticalMiles = this.Meters * 0.0005399565; break; case DistanceType.NauticalMiles: this.NauticalMiles = distance; this.Meters = this.NauticalMiles * 1852.001; this.Feet = this.Meters * 3.28084; this.Kilometers = this.Meters / 1000; this.Miles = this.Meters * 0.000621371; break; default: this.Kilometers = distance; this.Meters = distance * 1000; this.Feet = this.Meters * 3.28084; this.Miles = this.Meters * 0.000621371; this.NauticalMiles = this.Meters * 0.0005399565; break; } } private void Vincenty(Coordinate coord1, Coordinate coord2) { Double lat1, lat2, lon1, lon2; Double d, crs12; lat1 = coord1.Latitude.ToRadians(); lat2 = coord2.Latitude.ToRadians(); lon1 = coord1.Longitude.ToRadians() * -1; //REVERSE FOR CALC 2.1.1.1 lon2 = coord2.Longitude.ToRadians() * -1; //REVERSE FOR CALC 2.1.1.1 //Ensure datums match between coords if (coord1.equatorial_radius != coord2.equatorial_radius || coord1.inverse_flattening != coord2.inverse_flattening) { throw new InvalidOperationException("The datum set does not match between Coordinate objects."); } Double[] ellipse = new Double[] { coord1.equatorial_radius, coord1.inverse_flattening }; // elliptic code Double[] cde = Distance_Assistant.Dist_Ell(lat1, -lon1, lat2, -lon2, ellipse); // ellipse uses East negative crs12 = cde[1] * (180 / Math.PI); //Bearing _ = cde[2] * (180 / Math.PI); //Reverse Bearing d = cde[0]; //Distance this.Bearing = crs12; //reverseBearing = crs21; this.Meters = d; this.Kilometers = d / 1000; this.Feet = d * 3.28084; this.Miles = d * 0.000621371; this.NauticalMiles = d * 0.0005399565; } private void Haversine(Coordinate coord1, Coordinate coord2) { ////RADIANS Double lat1 = coord1.Latitude.ToRadians(); Double long1 = coord1.Longitude.ToRadians(); Double lat2 = coord2.Latitude.ToRadians(); Double long2 = coord2.Longitude.ToRadians(); //Distance Calcs Double R = 6371000; //6378137.0;//6371e3; //meters Double latRad = coord2.Latitude.ToRadians() - coord1.Latitude.ToRadians(); Double longRad = coord2.Longitude.ToRadians() - coord1.Longitude.ToRadians(); Double a = Math.Sin(latRad / 2.0) * Math.Sin(latRad / 2.0) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(longRad / 2.0) * Math.Sin(longRad / 2.0); Double cl = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); Double dist = R * cl; //Get bearing Double dLong = long2 - long1; Double y = Math.Sin(dLong) * Math.Cos(lat2); Double x = Math.Cos(lat1) * Math.Sin(lat2) - Math.Sin(lat1) * Math.Cos(lat2) * Math.Cos(dLong); Double brng = Math.Atan2(y, x) * (180 / Math.PI); //Convert bearing back to degrees. //if (brng < 0) { brng -= 180; brng = Math.Abs(brng); } brng = (brng + 360) % 360; //v2.1.1.1 NORMALIZE HEADING this.Kilometers = dist / 1000; this.Meters = dist; this.Feet = dist * 3.28084; this.Miles = dist * 0.000621371; this.NauticalMiles = dist * 0.0005399565; this.Bearing = brng; } /// /// Distance in Kilometers /// public Double Kilometers { get; private set; } /// /// Distance in Statute Miles /// public Double Miles { get; private set; } /// /// Distance in Nautical Miles /// public Double NauticalMiles { get; private set; } /// /// Distance in Meters /// public Double Meters { get; private set; } /// /// Distance in Feet /// public Double Feet { get; private set; } /// /// Initial Bearing from Coordinate 1 to Coordinate 2 /// public Double Bearing { get; private set; } } /// /// Distance measurement type /// public enum DistanceType { /// /// Distance in Meters /// Meters, /// /// Distance in Kilometers /// Kilometers, /// /// Distance in Feet /// Feet, /// /// Distance in Statute Miles /// Miles, /// /// Distance in Nautical Miles /// NauticalMiles } [Serializable] internal class Distance_Assistant { /// /// Returns new geodetic coordinate in radians /// /// Latitude in Radians /// Longitude in Radians /// Bearing /// Distance /// Earth Ellipse Values /// double[] public static Double[] Direct_Ell(Double glat1, Double glon1, Double faz, Double s, Double[] ellipse) { glon1 *= -1; //REVERSE LONG FOR CALC 2.1.1.1 Double EPS = 0.00000000005;//Used to determine if starting at pole. Double r, tu, sf, cf, b, cu, su, sa, c2a, x, c, d, y, sy = 0, cy = 0, cz = 0, e = 0; Double glat2, glon2, f; //Determine if near pole if (Math.Abs(Math.Cos(glat1)) < EPS && !(Math.Abs(Math.Sin(faz)) < EPS)) { Debug.WriteLine("Warning: Location is at earth's pole. Only N-S courses are meaningful at this location."); } Double a = ellipse[0];//Equitorial Radius f = 1 / ellipse[1];//Flattening r = 1 - f; tu = r * Math.Tan(glat1); sf = Math.Sin(faz); cf = Math.Cos(faz); b = cf == 0 ? 0.0 : 2.0 * Math.Atan2(tu, cf); cu = 1.0 / Math.Sqrt(1 + tu * tu); su = tu * cu; sa = cu * sf; c2a = 1 - sa * sa; x = 1.0 + Math.Sqrt(1.0 + c2a * (1.0 / (r * r) - 1.0)); x = (x - 2.0) / x; c = 1.0 - x; c = (x * x / 4.0 + 1.0) / c; d = (0.375 * x * x - 1.0) * x; tu = s / (r * a * c); y = tu; c = y + 1; while (Math.Abs(y - c) > EPS) { sy = Math.Sin(y); cy = Math.Cos(y); cz = Math.Cos(b + y); e = 2.0 * cz * cz - 1.0; c = y; x = e * cy; y = e + e - 1.0; y = (((sy * sy * 4.0 - 3.0) * y * cz * d / 6.0 + x) * d / 4.0 - cz) * sy * d + tu; } b = cu * cy * cf - su * sy; c = r * Math.Sqrt(sa * sa + b * b); d = su * cy + cu * sy * cf; glat2 = ModM.ModLat(Math.Atan2(d, c)); c = cu * cy - su * sy * cf; x = Math.Atan2(sy * sf, c); c = ((-3.0 * c2a + 4.0) * f + 4.0) * c2a * f / 16.0; d = ((e * cy * c + cz) * sy * c + y) * sa; glon2 = ModM.ModLon(glon1 + x - (1.0 - c) * d * f); //Adjust for IDL //baz = ModM.ModCrs(Math.Atan2(sa, b) + Math.PI); return new Double[] { glat2, glon2 }; } /// /// Returns new geodetic coordinate in radians /// /// Latitude in radians /// Longitude in radians /// Bearing /// Distance /// double[] public static Double[] Direct(Double lat1, Double lon1, Double crs12, Double d12) { lon1 *= -1; //REVERSE LONG FOR CALC 2.1.1.1 Double EPS = 0.00000000005;//Used to determine if near pole. Double dlon, lat, lon; d12 *= 0.0005399565; //convert meter to nm d12 /= 180 * 60 / Math.PI;//Convert to Radian //Determine if near pole if (Math.Abs(Math.Cos(lat1)) < EPS && !(Math.Abs(Math.Sin(crs12)) < EPS)) { Debug.WriteLine("Warning: Location is at earth's pole. Only N-S courses are meaningful at this location."); } lat = Math.Asin(Math.Sin(lat1) * Math.Cos(d12) + Math.Cos(lat1) * Math.Sin(d12) * Math.Cos(crs12)); if (Math.Abs(Math.Cos(lat)) < EPS) { lon = 0.0; //endpoint a pole } else { dlon = Math.Atan2(Math.Sin(crs12) * Math.Sin(d12) * Math.Cos(lat1), Math.Cos(d12) - Math.Sin(lat1) * Math.Sin(lat)); lon = ModM.Mod(lon1 - dlon + Math.PI, 2 * Math.PI) - Math.PI; } return new Double[] { lat, lon }; } public static Double[] Dist_Ell(Double glat1, Double glon1, Double glat2, Double glon2, Double[] ellipse) { Double a = ellipse[0]; //Equitorial Radius Double f = 1 / ellipse[1]; //Flattening Double r, tu1, tu2, cu1, su1, cu2, s1, b1, f1; Double sx = 0, cx = 0, sy = 0, cy = 0, y = 0, c2a = 0, cz = 0, e = 0; Double EPS = 0.00000000005; Double faz, baz, s; Double iter = 1; Double MAXITER = 100; if (glat1 + glat2 == 0.0 && Math.Abs(glon1 - glon2) == Math.PI) { Debug.WriteLine("Warning: Course and distance between antipodal points is undefined"); glat1 += 0.00001; // allow algorithm to complete } if (glat1 == glat2 && (glon1 == glon2 || Math.Abs(Math.Abs(glon1 - glon2) - 2 * Math.PI) < EPS)) { Debug.WriteLine("Warning: Points 1 and 2 are identical- course undefined"); //D //crs12 //crs21 return new Double[] { 0, 0, Math.PI }; } r = 1 - f; tu1 = r * Math.Tan(glat1); tu2 = r * Math.Tan(glat2); cu1 = 1.0 / Math.Sqrt(1.0 + tu1 * tu1); su1 = cu1 * tu1; cu2 = 1.0 / Math.Sqrt(1.0 + tu2 * tu2); s1 = cu1 * cu2; b1 = s1 * tu2; f1 = b1 * tu1; Double x = glon2 - glon1; Double d = x + 1; Double c; while (Math.Abs(d - x) > EPS && iter < MAXITER) { iter += 1; sx = Math.Sin(x); cx = Math.Cos(x); tu1 = cu2 * sx; tu2 = b1 - su1 * cu2 * cx; sy = Math.Sqrt(tu1 * tu1 + tu2 * tu2); cy = s1 * cx + f1; y = Math.Atan2(sy, cy); Double sa = s1 * sx / sy; c2a = 1 - sa * sa; cz = f1 + f1; if (c2a > 0.0) { cz = cy - cz / c2a; } e = cz * cz * 2.0 - 1.0; c = ((-3.0 * c2a + 4.0) * f + 4.0) * c2a * f / 16.0; d = x; x = ((e * cy * c + cz) * sy * c + y) * sa; x = (1.0 - c) * x * f + glon2 - glon1; } faz = ModM.ModCrs(Math.Atan2(tu1, tu2)); baz = ModM.ModCrs(Math.Atan2(cu1 * sx, b1 * cx - su1 * cu2) + Math.PI); x = Math.Sqrt((1 / (r * r) - 1) * c2a + 1); x += 1; x = (x - 2.0) / x; c = 1.0 - x; c = (x * x / 4.0 + 1.0) / c; d = (0.375 * x * x - 1.0) * x; x = e * cy; s = ((((sy * sy * 4.0 - 3.0) * (1.0 - e - e) * cz * d / 6.0 - x) * d / 4.0 + cz) * sy * d + y) * c * a * r; if (Math.Abs(iter - MAXITER) < EPS) { Debug.WriteLine("Warning: Distance algorithm did not converge"); } return new Double[] { s, faz, baz }; } } }