Skip to content

Commit f21213c

Browse files
committed
Add geodetic distance
1 parent 31c43cc commit f21213c

File tree

1 file changed

+155
-0
lines changed
  • src/NetFabric.Numerics.Geography/Geodetic2

1 file changed

+155
-0
lines changed

src/NetFabric.Numerics.Geography/Geodetic2/Point.cs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,159 @@ object IPoint<Point<TDatum, TAngleUnits, TAngle>>.this[int index]
109109
1 => Longitude,
110110
_ => Throw.ArgumentOutOfRangeException<object>(nameof(index), index, "index out of range")
111111
};
112+
}
113+
114+
/// <summary>
115+
/// Provides static methods for point operations.
116+
/// </summary>
117+
public static class Point
118+
{
119+
/// <summary>
120+
/// Calculates the distance between two geodetic points.
121+
/// </summary>
122+
/// <param name="from">The first geodetic point.</param>
123+
/// <param name="to">The second geodetic point.</param>
124+
/// <returns>The distance between the two geodetic points, in meters.</returns>
125+
/// <remarks>
126+
/// <para>
127+
/// This method calculates the distance between two geodesic points on the Earth's surface using the spherical formula.
128+
/// The geodesic points are specified by their latitude and longitude coordinates in degrees.
129+
/// </para>
130+
/// <para>
131+
/// The distance is calculated by treating the Earth as a perfect sphere, which is a simplification and may introduce
132+
/// some degree of error for large distances or near the poles. The result is returned in kilometers.
133+
/// </para>
134+
/// <para>
135+
/// Note: The result of this method represents the shortest distance between the two points along the surface of the
136+
/// sphere, also known as the great-circle distance.
137+
/// </para>
138+
/// </remarks>
139+
public static TAngle DistanceSpherical<TDatum, TAngle>(Point<TDatum, Radians, TAngle> from, Point<TDatum, Radians, TAngle> to)
140+
where TDatum : IDatum<TDatum>
141+
where TAngle : struct, IFloatingPointIeee754<TAngle>, IMinMaxValue<TAngle>
142+
{
143+
var half = TAngle.CreateChecked(0.5);
144+
var halfLatitudeDifference = half * (to.Latitude - from.Latitude);
145+
var halfLongitudeDifference = half * (to.Latitude - from.Latitude);
146+
var a = (Angle.Sin(halfLatitudeDifference) * Angle.Sin(halfLatitudeDifference)) +
147+
(Angle.Cos(from.Latitude) * Angle.Cos(to.Latitude) *
148+
Angle.Sin(halfLongitudeDifference) * Angle.Sin(halfLongitudeDifference));
149+
var c = TAngle.CreateChecked(2) * Angle.Atan2(TAngle.Sqrt(a), TAngle.Sqrt(TAngle.One - a));
150+
151+
return TAngle.CreateChecked(MedianRadius(TDatum.Ellipsoid)) * c.Value;
152+
153+
static double MedianRadius(Ellipsoid ellipsoid)
154+
{
155+
var semiMajorAxis = ellipsoid.EquatorialRadius;
156+
var flatteningInverse = 1.0 / ellipsoid.Flattening;
157+
var semiMinorAxis = semiMajorAxis * (1 - flatteningInverse);
158+
159+
return double.Sqrt(semiMajorAxis * semiMinorAxis);
160+
}
161+
}
162+
163+
/// <summary>
164+
/// Calculates the distance between two geodetic points.
165+
/// </summary>
166+
/// <param name="from">The first geodetic point.</param>
167+
/// <param name="to">The second geodetic point.</param>
168+
/// <returns>The distance between the two geodetic points, in meters.</returns>
169+
/// <exception cref="InvalidOperationException">The iteration did not converge.</exception>"
170+
/// <remarks>
171+
/// <para>
172+
/// This method calculates the distance between two geodetic points on the Earth's surface using the datum equatorial radius
173+
/// and flattening. The geodetic points are defined by their latitude and longitude coordinates. The calculation assumes the Earth
174+
/// is an ellipsoid, and the provided equatorial radius and flattening define its shape. The resulting distance is returned in meters.
175+
/// </para>
176+
/// <para>
177+
/// The algorithm performs an iterative procedure to converge to the accurate distance calculation. In rare cases where the
178+
/// iteration does not converge within the defined limit, an <see cref="InvalidOperationException"/> is thrown.
179+
/// </para>
180+
/// </remarks>
181+
public static TAngle DistanceEllipsoid<TDatum, TAngle>(Point<TDatum, Radians, TAngle> from, Point<TDatum, Radians, TAngle> to)
182+
where TDatum : IDatum<TDatum>
183+
where TAngle : struct, IFloatingPointIeee754<TAngle>, IMinMaxValue<TAngle>
184+
{
185+
var latitudeDifference = to.Latitude - from.Latitude;
186+
var longitudeDifference = to.Longitude - from.Longitude;
187+
188+
var half = TAngle.CreateChecked(0.5);
189+
var halfLatitudeDifference = half * latitudeDifference;
190+
var halfLongitudeDifference = half * longitudeDifference;
191+
var a = (Angle.Sin(halfLatitudeDifference) * Angle.Sin(halfLatitudeDifference)) +
192+
(Angle.Cos(from.Latitude) * Angle.Cos(to.Latitude) *
193+
Angle.Sin(halfLongitudeDifference) * Angle.Sin(halfLongitudeDifference));
194+
var c = TAngle.CreateChecked(2) * Angle.Atan2(TAngle.Sqrt(a), TAngle.Sqrt(TAngle.One - a));
195+
196+
var semiMajorAxis = TDatum.Ellipsoid.EquatorialRadius;
197+
var flatteningInverse = 1.0 / TDatum.Ellipsoid.Flattening;
198+
var semiMinorAxis = semiMajorAxis * (1 - flatteningInverse);
199+
200+
var uSquared = TAngle.CreateChecked(((semiMajorAxis * semiMajorAxis) - (semiMinorAxis * semiMinorAxis)) / (semiMinorAxis * semiMinorAxis));
201+
202+
var sinU1 = Angle.Sin(from.Latitude);
203+
var cosU1 = Angle.Cos(from.Latitude);
204+
var sinU2 = Angle.Sin(to.Latitude);
205+
var cosU2 = Angle.Cos(to.Latitude);
206+
207+
var lambda = longitudeDifference;
208+
209+
var iterationLimit = 100;
210+
var cosLambda = TAngle.Zero;
211+
var sinLambda = TAngle.Zero;
212+
Angle<Radians, TAngle> sigma;
213+
TAngle cosSigma, sinSigma, cos2SigmaM, sinSigmaPrev;
214+
TAngle sigmaP = TAngle.Zero;
215+
216+
do
217+
{
218+
sinLambda = Angle.Sin(lambda);
219+
cosLambda = Angle.Cos(lambda);
220+
sinSigma = TAngle.Sqrt((cosU2 * sinLambda * (cosU2 * sinLambda)) +
221+
(((cosU1 * sinU2) - (sinU1 * cosU2 * cosLambda)) * ((cosU1 * sinU2) - (sinU1 * cosU2 * cosLambda))));
222+
223+
if (sinSigma == TAngle.Zero)
224+
return TAngle.Zero; // Coincident points
225+
226+
cosSigma = (sinU1 * sinU2) + cosU1 * cosU2 * cosLambda;
227+
sigma = Angle.Atan2(sinSigma, cosSigma);
228+
sinSigmaPrev = sinSigma;
229+
230+
cos2SigmaM = cosSigma - (TAngle.CreateChecked(2) * sinU1 * sinU2 / ((cosU1 * cosU2) + (sinU1 * sinU2)));
231+
232+
var cSquared = uSquared * cosSigma * cosSigma;
233+
var lambdaP = lambda;
234+
lambda = longitudeDifference + ((1 - cSquared) * uSquared * sinSigma *
235+
(sigma + (uSquared * sinSigmaPrev * (cos2SigmaM +
236+
(uSquared * cosSigma * (-1 + (2 * cos2SigmaM * cos2SigmaM)))))));
237+
}
238+
while (TAngle.Abs((lambda - lambdaP) / lambda) > 1e-12 && --iterationLimit > 0);
239+
240+
if (iterationLimit == 0)
241+
throw new InvalidOperationException("Distance calculation did not converge.");
242+
243+
var uSquaredTimesC = uSquared * cSquared;
244+
var aTimesB = semiMinorAxis * semiMinorAxis * cosSigma * cosSigma;
245+
var bTimesA = semiMajorAxis * semiMajorAxis * sinSigma * sinSigmaPrev;
246+
var sigmaP2 = sigmaP;
247+
sigmaP = sigma;
248+
249+
var phi = Angle.Atan2(semiMinorAxis * cosU1 * sinLambda, semiMajorAxis * cosU1 * cosLambda);
250+
251+
var sinPhi = Angle.Sin(phi);
252+
var cosPhi = Angle.Cos(phi);
253+
254+
var x = Angle.Atan2((semiMinorAxis / semiMajorAxis) * sinPhi + aTimesB * sinSigma * (cos2SigmaM +
255+
aTimesB * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) / 4), (1 - uSquared) * (sinPhi - bTimesA *
256+
sinSigmaPrev * (cos2SigmaM - bTimesA * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) / 4)));
257+
258+
var y = Angle.Atan2((1 - uSquared) * sinPhi + uSquaredTimesC * sinSigma * (cosSigma - uSquared *
259+
sinSigmaPrev * (cos2SigmaM - uSquared * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) / 4)), (semiMajorAxis /
260+
semiMinorAxis) * (cosPhi - bTimesA * sinSigma * (cos2SigmaM - bTimesA * cosSigma * (-1 + 2 *
261+
cos2SigmaM * cos2SigmaM) / 4)));
262+
263+
var z = TAngle.Sqrt(x * x + y * y) * TAngle.Sign((semiMinorAxis - semiMajorAxis) * sinSigma * sinSigmaPrev);
264+
265+
return TAngle.Sqrt(x * x + y * y + z * z);
266+
}
112267
}

0 commit comments

Comments
 (0)