@@ -109,4 +109,159 @@ object IPoint<Point<TDatum, TAngleUnits, TAngle>>.this[int index]
109
109
1 => Longitude ,
110
110
_ => Throw . ArgumentOutOfRangeException < object > ( nameof ( index ) , index , "index out of range" )
111
111
} ;
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
+ }
112
267
}
0 commit comments