/// <summary> /// Convert a latitude/longitude coordinate to a Euclidian coordinate on a flat map /// </summary> /// <param name="coordinates">The latitude/longitude coordinates in degrees</param> /// <returns>The euclidian coordinates of that point</returns> public abstract EuclidianCoordinate ToEuclidian(GlobalCoordinates coordinates);
/// <summary> /// Compute the euclidian distance between two points given by rectangular coordinates. /// Please note, that due to scaling effects this might be quite different from the true /// geodetic distance. To get a good approximation, you must divide this value by a /// scale factor. /// </summary> /// <param name="point1">The first point</param> /// <param name="point2">The second point</param> /// <returns>The distance between the points</returns> public double EuclidianDistance(GlobalCoordinates point1, GlobalCoordinates point2) { return(EuclidianDistance(ToEuclidian(point1), ToEuclidian(point2))); }
/// <summary> /// Get the Mercator scale factor for the given point /// </summary> /// <param name="point">The point</param> /// <returns>The scale factor</returns> public abstract double ScaleFactor(GlobalCoordinates point);
/// <summary> /// Get the Mercator scale factor for the given point /// </summary> /// <param name="point">The point</param> /// <returns>The scale factor</returns> public override double ScaleFactor(GlobalCoordinates point) { return(ScaleFactor(point.Latitude.Degrees)); }
/// <summary> /// Convert coordinates to an XY position on a Mercator map /// </summary> /// <param name="coordinates">The coordinates on the globe</param> /// <returns>An array of two doubles, the first is X, the second Y</returns> public double[] GlobalCoordinatesToXy(GlobalCoordinates coordinates) { var e = ToEuclidian(coordinates); return(new[] { e.X, e.Y }); }
/// <summary> /// Calculate the destination and final bearing after traveling a specified /// distance, and a specified starting bearing, for an initial location. /// This is the solution to the direct geodetic problem. /// </summary> /// <param name="start">starting location</param> /// <param name="startBearing">starting bearing (degrees)</param> /// <param name="distance">distance to travel (meters)</param> /// <param name="endBearing">bearing at destination (degrees)</param> /// <returns>The coordinates of the final location of the traveling</returns> public GlobalCoordinates CalculateEndingGlobalCoordinates( GlobalCoordinates start, Angle startBearing, double distance, out Angle endBearing) { var majorAxis = ReferenceGlobe.SemiMajorAxis; var minorAxis = ReferenceGlobe.SemiMinorAxis; var aSquared = majorAxis * majorAxis; var bSquared = minorAxis * minorAxis; var flattening = ReferenceGlobe.Flattening; var phi1 = start.Latitude.Radians; var alpha1 = startBearing.Radians; var cosAlpha1 = Math.Cos(alpha1); var sinAlpha1 = Math.Sin(alpha1); var s = distance; var tanU1 = (1.0 - flattening) * Math.Tan(phi1); var cosU1 = 1.0 / Math.Sqrt(1.0 + tanU1 * tanU1); var sinU1 = tanU1 * cosU1; if (Math.Sign(distance) < 0) { throw new ArgumentOutOfRangeException(Resources.NEGATIVE_DISTANCE); } // eq. 1 var sigma1 = Math.Atan2(tanU1, cosAlpha1); // eq. 2 var sinAlpha = cosU1 * sinAlpha1; var sin2Alpha = sinAlpha * sinAlpha; var cos2Alpha = 1 - sin2Alpha; var uSquared = cos2Alpha * (aSquared - bSquared) / bSquared; // eq. 3 var a = 1 + (uSquared / 16384) * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared))); // eq. 4 var b = (uSquared / 1024) * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared))); // iterate until there is a negligible change in sigma double sinSigma; double sigmaM2; double cosSigmaM2; double cos2SigmaM2; var sOverbA = s / (minorAxis * a); var sigma = sOverbA; var prevSigma = sOverbA; for (;;) { // eq. 5 sigmaM2 = 2.0 * sigma1 + sigma; cosSigmaM2 = Math.Cos(sigmaM2); cos2SigmaM2 = cosSigmaM2 * cosSigmaM2; sinSigma = Math.Sin(sigma); var cosSignma = Math.Cos(sigma); // eq. 6 var deltaSigma = b * sinSigma * (cosSigmaM2 + (b / 4.0) * (cosSignma * (-1 + 2 * cos2SigmaM2) - (b / 6.0) * cosSigmaM2 * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM2))); // eq. 7 sigma = sOverbA + deltaSigma; // break after converging to tolerance if (sigma.IsApproximatelyEqual(prevSigma, Precision)) { break; } prevSigma = sigma; } sigmaM2 = 2.0 * sigma1 + sigma; cosSigmaM2 = Math.Cos(sigmaM2); cos2SigmaM2 = cosSigmaM2 * cosSigmaM2; var cosSigma = Math.Cos(sigma); sinSigma = Math.Sin(sigma); // eq. 8 var phi2 = Math.Atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1.0 - flattening) * Math.Sqrt(sin2Alpha + Math.Pow(sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1, 2.0))); // eq. 9 // This fixes the pole crossing defect spotted by Matt Feemster. When a path // passes a pole and essentially crosses a line of latitude twice - once in // each direction - the longitude calculation got messed up. Using Atan2 // instead of Atan fixes the defect. The change is in the next 3 lines. //double tanLambda = sinSigma * sinAlpha1 / (cosU1 * cosSigma - sinU1*sinSigma*cosAlpha1); //double lambda = Math.Atan(tanLambda); var lambda = Math.Atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1); // eq. 10 var c = (flattening / 16) * cos2Alpha * (4 + flattening * (4 - 3 * cos2Alpha)); // eq. 11 var l = lambda - (1 - c) * flattening * sinAlpha * (sigma + c * sinSigma * (cosSigmaM2 + c * cosSigma * (-1 + 2 * cos2SigmaM2))); // eq. 12 var alpha2 = Math.Atan2(sinAlpha, -sinU1 * sinSigma + cosU1 * cosSigma * cosAlpha1); // build result var latitude = new Angle(); var longitude = new Angle(); latitude.Radians = phi2; longitude.Radians = start.Longitude.Radians + l; endBearing = Angle.Zero; endBearing.Radians = alpha2; return(new GlobalCoordinates(latitude, longitude)); }
/// <summary> /// Compute a loxodromic path from start to end witha given number of points /// </summary> /// <param name="start">starting coordinates</param> /// <param name="end">ending coordinates</param> /// <param name="mercatorRhumbDistance">The distance of the two points on a Rhumb line on the Mercator projection</param> /// <param name="bearing">The constant course for the path</param> /// <param name="numberOfPoints">Number of points on the path (including start and end)</param> /// <exception cref="ArgumentOutOfRangeException">Thrown if the number of points is less than 2</exception> /// <returns>An array of points describing the loxodromic path from start to end</returns> public GlobalCoordinates[] CalculatePath( GlobalCoordinates start, GlobalCoordinates end, out double mercatorRhumbDistance, out Angle bearing, int numberOfPoints = 10) { mercatorRhumbDistance = 0; bearing = 0; if (numberOfPoints < 2) { throw new ArgumentOutOfRangeException(Resources.GEODETIC_PATH_MIN_2); } if (start == end || numberOfPoints == 2) { return new[] { start, end } } ; var cStart = ToEuclidian(start); var cEnd = ToEuclidian(end); var dist = EuclidianDistance(cStart, cEnd); var step = dist / (numberOfPoints - 1); var dx = (cEnd.X - cStart.X) / dist; var dy = (cEnd.Y - cStart.Y) / dist; bearing = 0; bearing.Radians = Math.Atan2(dx, dy); if (bearing < 0) { bearing += 360; } if (bearing == 90 || bearing == 270) { mercatorRhumbDistance = dist * ScaleFactor(start.Latitude.Degrees); } else { /* * This is based on a paper published by Miljenko Petrović * See: http://hrcak.srce.hr/file/24998 * */ var e2 = ReferenceGlobe.Eccentricity * ReferenceGlobe.Eccentricity; mercatorRhumbDistance = ReferenceGlobe.SemiMajorAxis / Math.Cos(bearing.Radians) * (((1 - e2 / 4.0) * (end.Latitude - start.Latitude).Radians) - e2 * (Math.Sin(2 * end.Latitude.Radians) - Math.Sin(2 * start.Latitude.Radians)) * 3.0 / 8.0); } var result = new GlobalCoordinates[numberOfPoints]; result[0] = start; result[numberOfPoints - 1] = end; for (var i = 1; i < numberOfPoints - 1; i++) { var point = new EuclidianCoordinate(this, cStart.X + i * dx * step, cStart.Y + i * dy * step); result[i] = FromEuclidian(point); } return(result); } }
/// <summary> /// Creates a new instance of GlobalPosition for a position on the surface of /// the reference ellipsoid. /// </summary> /// <param name="coords"></param> public GlobalPosition(GlobalCoordinates coords) : this(coords, 0.0) { }
/// <summary> /// Calculate the geodetic curve between two points on a specified reference ellipsoid. /// This is the solution to the inverse geodetic problem. /// </summary> /// <param name="start">starting coordinates</param> /// <param name="end">ending coordinates </param> /// <returns>The geodetic curve information to get from start to end</returns> public GeodeticCurve CalculateGeodeticCurve( GlobalCoordinates start, GlobalCoordinates end) { // // All equation numbers refer back to Vincenty's publication: // See http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf // // get constants var majorAxis = ReferenceGlobe.SemiMajorAxis; var minorAxis = ReferenceGlobe.SemiMinorAxis; var flattening = ReferenceGlobe.Flattening; // get parameters as radians var phi1 = start.Latitude.Radians; var lambda1 = start.Longitude.Radians; var phi2 = end.Latitude.Radians; var lambda2 = end.Longitude.Radians; // calculations var a2 = majorAxis * majorAxis; var b2 = minorAxis * minorAxis; var squaredRatio = (a2 - b2) / b2; var omega = lambda2 - lambda1; var tanphi1 = Math.Tan(phi1); var tanU1 = (1.0 - flattening) * tanphi1; var u1 = Math.Atan(tanU1); var sinU1 = Math.Sin(u1); var cosU1 = Math.Cos(u1); var tanphi2 = Math.Tan(phi2); var tanU2 = (1.0 - flattening) * tanphi2; var u2 = Math.Atan(tanU2); var sinU2 = Math.Sin(u2); var cosU2 = Math.Cos(u2); var sinU1SinU2 = sinU1 * sinU2; var cosU1SinU2 = cosU1 * sinU2; var sinU1CosU2 = sinU1 * cosU2; var cosU1CosU2 = cosU1 * cosU2; // eq. 13 var lambda = omega; // intermediates we'll need to compute 's' var a = 0.0; var sigma = 0.0; var deltasigma = 0.0; var converged = false; for (var i = 0; i < 20; i++) { var lambda0 = lambda; var b = 0.0; var sinlambda = Math.Sin(lambda); var coslambda = Math.Cos(lambda); // eq. 14 var sin2Sigma = (cosU2 * sinlambda * cosU2 * sinlambda) + Math.Pow(cosU1SinU2 - sinU1CosU2 * coslambda, 2.0); var sinsigma = Math.Sqrt(sin2Sigma); // eq. 15 var cossigma = sinU1SinU2 + (cosU1CosU2 * coslambda); // eq. 16 sigma = Math.Atan2(sinsigma, cossigma); // eq. 17 Careful! sin2sigma might be almost 0! var sinalpha = sin2Sigma.IsZero() ? 0.0 : cosU1CosU2 * sinlambda / sinsigma; var alpha = Math.Asin(sinalpha); var cosalpha = Math.Cos(alpha); var cos2Alpha = cosalpha * cosalpha; // eq. 18 Careful! cos2alpha might be almost 0! var cos2Sigmam = cos2Alpha.IsZero() ? 0.0 : cossigma - 2 * sinU1SinU2 / cos2Alpha; var u3 = cos2Alpha * squaredRatio; var cos2Sigmam2 = cos2Sigmam * cos2Sigmam; // eq. 3 a = 1.0 + u3 / 16384 * (4096 + u3 * (-768 + u3 * (320 - 175 * u3))); // eq. 4 b = u3 / 1024 * (256 + u3 * (-128 + u3 * (74 - 47 * u3))); // eq. 6 deltasigma = b * sinsigma * (cos2Sigmam + b / 4 * (cossigma * (-1 + 2 * cos2Sigmam2) - b / 6 * cos2Sigmam * (-3 + 4 * sin2Sigma) * (-3 + 4 * cos2Sigmam2))); // eq. 10 var c = flattening / 16 * cos2Alpha * (4 + flattening * (4 - 3 * cos2Alpha)); // eq. 11 (modified) lambda = omega + (1 - c) * flattening * sinalpha * (sigma + c * sinsigma * (cos2Sigmam + c * cossigma * (-1 + 2 * cos2Sigmam2))); // see how much improvement we got var change = Math.Abs((lambda - lambda0) / lambda); if ((i > 1) && (change < Precision)) { converged = true; break; } } // eq. 19 var s = minorAxis * a * (sigma - deltasigma); Angle alpha1; // didn't converge? must be N/S if (!converged) { if (phi1 > phi2) { alpha1 = Angle.Angle180; } else if (phi1 < phi2) { alpha1 = Angle.Zero; } else { alpha1 = new Angle(double.NaN); } } // else, it converged, so do the math else { alpha1 = new Angle(); // eq. 20 var radians = Math.Atan2(cosU2 * Math.Sin(lambda), (cosU1SinU2 - sinU1CosU2 * Math.Cos(lambda))); if (radians.IsNegative()) { radians += TwoPi; } alpha1.Radians = radians; } if (alpha1 >= 360.0) { alpha1 -= 360.0; } return(new GeodeticCurve(this, s, alpha1)); }
/// <summary> /// Creates a new instance of GlobalPosition. /// </summary> /// <param name="coords">coordinates on the reference ellipsoid.</param> /// <param name="elevation">elevation, in meters, above the reference ellipsoid.</param> public GlobalPosition(GlobalCoordinates coords, double elevation) { _mCoordinates = coords; Elevation = elevation; }
/// <summary> /// Get the globally unique Mesh number of a location given by /// latitude and longitude. /// </summary> /// <param name="coord">The location to convert</param> /// <returns>The mesh number to which the location belongs</returns> public long MeshNumber(GlobalCoordinates coord) { return(MeshNumber((UtmCoordinate)Projection.ToEuclidian(coord))); }
/// <summary> /// Compute the meridian convergence for a location /// </summary> /// <param name="point">The location defined by latitude/longitude</param> /// <returns>The meridian convergence</returns> public Angle MeridianConvergence(GlobalCoordinates point) { var c = ToUtmCoordinates(point, out double scaleFactor, out double meridianConvergence); return(Angle.RadToDeg(meridianConvergence)); }
/// <summary> /// Get the Mercator scale factor for the given point /// </summary> /// <param name="point">The point</param> /// <returns>The scale factor</returns> public override double ScaleFactor(GlobalCoordinates point) { var c = ToUtmCoordinates(point, out double scaleFactor, out double meridianConvergence); return(scaleFactor); }
/// <summary> /// Convert a latitude/longitude coordinate to a Euclidian coordinate on a flat map /// </summary> /// <param name="coordinates">The latitude/longitude coordinates in degrees</param> /// <returns>The euclidian coordinates of that point</returns> public override EuclidianCoordinate ToEuclidian(GlobalCoordinates coordinates) { return(ToUtmCoordinates(coordinates, out double scaleFactor, out double meridianConvergence)); }