-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGeoDistance.java
More file actions
275 lines (241 loc) · 13.1 KB
/
GeoDistance.java
File metadata and controls
275 lines (241 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/******************************************************************************
Module : GeoDistance.java |Class Lib
Description : Methods to calculate the great circle (orthodromic) distance
: between two geo-points on Earth
Version : 21.1.001
------------------------------------------------------------------------------
Copyright : 2011-2025 Alexander Bell
-------------------------------------------------------------------------------
DISCLAIMER : This Module is provided on AS IS basis without any warranty.
: The user assumes the entire risk as to the accuracy and the
: use of this module. In no event shall the author be liable
: for any damages arising out of the use of or inability
: to use this module.
TERMS OF USE : This module is copyrighted.
: Please keep the Copyright notice intact.
****************************************************************************/
import java.lang.Math;
/// <summary>
/// Class GeoDistance contains four static methods to calculate the
/// great-circle (orthodromic) distance between two geo-points on Earth
/// specified by coordinates in decimal format (Latitude, Longitude), e.g.
/// John F. Kennedy International Airport (JFK): {40.641766,-73.780968},
/// Los Angeles International Airport (LAX): {33.942791,-118.410042}
/// Sample output:
/// =======================================================================
/// Great-circle (orthodromic) distance between two geo-points:
/// JFK {40.641766,-73.780968} to LHR {51.470020,-0.454295}
/// km --------------------------------------------------------------------
/// Haversine : 5540.175419079547 (high accuracy)
/// Spherical Law of Cosines : 5540.175419079548 (high accuracy)
/// Inverse Vincenty : 5555.065686009474 (highest accuracy)
/// Spherical Earth Projection : 5784.908563389233 (lower accuracy)
/// Expected value :~5554.5 km
/// miles -----------------------------------------------------------------
/// Haversine : 3442.5054053574295 (high accuracy)
/// Spherical Law of Cosines : 3442.5054053574304 (high accuracy)
/// Inverse Vincenty : 3451.7577882724104 (highest accuracy)
/// Spherical Earth Projection : 3594.5755310171303 (lower accuracy)
/// Expected value :~3451.4 miles
/// =======================================================================
/// </summary>
public class GeoDistance {
//region public enum
public enum Units { SI, US } // SI: km, US: miles
//endregion
//region private const
// Earth mean radius, km
private static final double meanRadius = 6371.009;
// Conversion factor mile to km
private static final double mi2km = 1.609344;
//endregion
//region Haversine algorithm **********************************************************
/// <summary>
/// Haversine algorithm implemented in this method enables
/// the high-accuracy geodesic calculation of the great-circle
/// (aka orthodromic) distance (km/miles) between two geographic
/// points on the Earth's surface.
/// </summary>
/// <param name="Lat1">double: 1st point Latitude</param>
/// <param name="Lon1">double: 1st point Longitude</param>
/// <param name="Lat2">double: 2nd point Latitude</param>
/// <param name="Lon2">double: 2nd point Longitude</param>
/// <returns>double: distance, km/miles</returns>
public static double Haversine(double Lat1, double Lon1,
double Lat2, double Lon2,
Units Unit) {
try {
// convert coordinates' latitude to radians
double φ1 = Math.toRadians(Lat1);
double φ2 = Math.toRadians(Lat2);
double _a = Math.sin((φ2 - φ1)/2);
_a *= _a; // square
double _b = Math.sin(Math.toRadians((Lon2 - Lon1)/2));
_b *= _b * Math.cos(φ1) * Math.cos(φ2);
// central angle θ, aka arc segment angular distance
double θ = 2* Math.asin(Math.sqrt(_a + _b));
// great-circle (orthodromic) distance, km/miles
return θ * (Unit == Units.SI? 1:1/ mi2km) * meanRadius;
}
catch( Exception e) { return -1; } // indicates error
}
//endregion
//region Spherical Law of Cosines *****************************************************
/// <summary>
/// Spherical Law of Cosines (SLC) algorithm implemented in this
/// method enables the high-accuracy geodesic calculation of the
/// great-circle (aka orthodromic) distance (km/miles) between
/// two geographic points on the Earth's surface.
/// Note: results are very close to the Haversine formula, which is
/// generally preferred for numerical stability with small distances.
/// </summary>
/// <param name="Lat1">double: 1st point Latitude</param>
/// <param name="Lon1">double: 1st point Longitude</param>
/// <param name="Lat2">double: 2nd point Latitude</param>
/// <param name="Lon2">double: 2nd point Longitude</param>
/// <returns>double: distance, km/miles</returns>
public static double SLC(double Lat1, double Lon1,
double Lat2, double Lon2,
Units Unit) {
try {
// convert coordinates to radians
double φ1 = Math.toRadians(Lat1); // Lat1;
double φ2 = Math.toRadians(Lat2); // Lat2;
double Δλ = Math.toRadians(Lon1-Lon2); // delta Lon;
// central angle θ, aka arc segment angular distance
double θ = Math.acos(Math.sin(φ1) * Math.sin(φ2) +
Math.cos(φ1) * Math.cos(φ2) * Math.cos(Δλ));
// great-circle (orthodromic) distance, km/miles
return θ * (Unit == Units.SI? 1:1/ mi2km) * meanRadius;
}
catch( Exception e) { return -1; } // indicates error
}
//endregion
//region Vincenty algorithm (ellipsoid) ***********************************************
/// <summary>
/// Inverse Vincenty (ellipsoid) algorithm implemented in this method enables
/// the very high-accuracy geodesic calculation of the great-circle (orthodromic)
/// distance (km/miles) between two geographic points on the Earth's surface.
/// Notes -----------------------------------------------------------------------------
/// Inverse Vincenty (ellipsoid) algorithm provides the highest accuracy among
/// the common spherical/ellipsoidal computational methods, but it is not a closed-form.
/// This inverse solution (distance and bearings between two points) is an efficient
/// iterative algorithm with nested expressions well-suited for
/// the software implementation.
/// Regarding the accuracy and robustness of the method, see the practical notes below.
/// - Convergence:
/// The inverse method can fail near antipodal points.
/// Use a max-iteration guard and a small epsilon; if it fails, fall back
/// to a more robust geodesic algorithm.
/// - Precision:
/// Double precision is sufficient; avoid premature rounding of inputs.
/// Keep lat/lon in radians for the loop.
/// - Model choice:
/// WGS84 is standard. For a different datum (e.g., GRS80), set 𝑎 and 𝑓 accordingly.
/// - Outputs:
/// Besides distance, this method can return initial/final bearings.
/// - AI vibe coding:
/// This Inverse Vincenty geodesic algorithm was implemented in AI-assisted
/// pair programming (vibe coding) interactive session with AI Copilot.
/// -----------------------------------------------------------------------------------
/// </summary>
/// <returns>double: orthodromic distance, km/miles</returns>
public static double Vincenty(double Lat1, double Lon1,
double Lat2, double Lon2,
Units Unit) {
// WGS84 constants
double a = 6378137.0; // Earth equatorial radius, m
double f = 1.0 / 298.257223563;
double b = a * (1.0 - f);
try {
// Convert to radians
double φ1 = Math.toRadians(Lat1), φ2 = Math.toRadians(Lat2);
double Δλ = Math.toRadians(Lon2 - Lon1);
// Reduced latitudes
double U1 = Math.atan((1 - f) * Math.tan(φ1));
double U2 = Math.atan((1 - f) * Math.tan(φ2));
double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
double λ = Δλ;
double λPrev;
double iterLimit = 100;
double ε = 1e-12;
double sinσ, cosσ, σ, sinα, cos2α, cos2σm;
double u2, A, B, Δσ;
do {
double sinλ = Math.sin(λ), cosλ = Math.cos(λ);
double term1 = cosU2 * sinλ;
double term2 = cosU1 * sinU2 - sinU1 * cosU2 * cosλ;
sinσ = Math.sqrt(term1 * term1 + term2 * term2);
if (sinσ == 0.0) return 0.0; // coincident points
cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
σ = Math.atan2(sinσ, cosσ);
sinα = (cosU1 * cosU2 * sinλ) / sinσ;
double sin2α = sinα * sinα;
cos2α = 1.0 - sin2α;
if (cos2α != 0.0) cos2σm = cosσ - (2.0 * sinU1 * sinU2) / cos2α;
else cos2σm = 0.0; // equatorial line
u2 = (cos2α * (a * a - b * b)) / (b * b);
A = 1.0 + (u2 / 16384.0) * (4096.0 + u2 * (-768.0 + u2 *
(320.0 - 175.0 * u2)));
B = (u2 / 1024.0) * (256.0 + u2 * (-128.0 + u2 * (74.0 - 47.0 * u2)));
double cos2σm2 = cos2σm * cos2σm;
Δσ = B * sinσ * (cos2σm + (B / 4.0) * (cosσ * (-1.0 + 2.0 * cos2σm2)
- (B / 6.0) * cos2σm * (-3.0 + 4.0 * sinσ * sinσ) *
(-3.0 + 4.0 * cos2σm2)));
double C = (f / 16.0) * cos2α * (4.0 + f * (4.0 - 3.0 * cos2α));
λPrev = λ;
λ = Δλ + (1.0 - C) * f * sinα *
(σ + C * sinσ * (cos2σm + C * cosσ * (-1.0 + 2.0 * cos2σm2)));
if (Math.abs(λ - λPrev) < ε) break;
} while (--iterLimit > 0);
// If not converged, you may fall back to a robust solver
if (iterLimit == 0) throw new ArithmeticException("No Convergence");
double s = b * A * (σ - Δσ);
// Optional: initial α1 and final α2 bearings calculation
double α1 = Math.atan2(cosU2 * Math.sin(λ),
cosU1 * sinU2 - sinU1 * cosU2 * Math.cos(λ));
double α2 = Math.atan2(cosU1 * Math.sin(λ),
-sinU1 * cosU2 + cosU1 * sinU2 * Math.cos(λ));
// orthodromic distance, km/miles
return s * (Unit == Units.SI? 1:1/ mi2km)/1000;
}
catch( Exception e) { return -1; } //indicates error
}
//endregion
//region Spherical Earth Projection (SEP) *********************************************
/// <summary>
/// Spherical Earth Projection (SEP) to a plane formula
/// implemented in this method enables the calculation
/// of a great-circle (orthodromic) distance(km/miles) between two
/// geographic points on the Earth using Pythagorean Theorem:
/// Central Angle = Sqrt((φ2 - φ1)^2 + (Cos((φ1 + φ2)/2) * (Lon2 - Lon1))^2)
/// Note: this is a relatively low accuracy computation approach
/// suitable for small distances (e.g., within a city or small region);
/// it is shown mostly for a didactic purpose. For higher accuracy over
/// longer distances, use either Haversine, or Spherical Law of Cosines,
/// or Inverse Vincenty methods (the latter provides the highest accuracy).
/// </summary>
/// <param name="Lat1">double: 1st point Latitude</param>
/// <param name="Lon1">double: 1st point Longitude</param>
/// <param name="Lat2">double: 2nd point Latitude</param>
/// <param name="Lon2">double: 2nd point Longitude</param>
/// <returns>double: distance, km/miles</returns>
public static double SEP(double Lat1, double Lon1,
double Lat2, double Lon2,
Units UnitSys){
try {
// convert to radians
double φ1 = Math.toRadians(Lat1);
double φ2 = Math.toRadians(Lat2);
double Δφ = Math.toRadians(Lat2 - Lat1);
double _a = Math.toRadians((Lon2 - Lon1) * Math.cos((φ1 + φ2) / 2));
// central angle θ (arc segment angular distance)
double θ = Math.sqrt(_a * _a + Δφ * Δφ);
// great-circle (orthodromic) distance, km/miles
return θ * (UnitSys == Units.SI ? 1 : 1 / mi2km) * meanRadius;
}
catch( Exception e) { return -1; } // indicates error
}
//endregion
}