Java中計算兩個坐標之間的距離
瀏覽人數:1,464最近更新:
1. 概述
在本快速教程中,我們將實現計算兩個地理坐標之間的距離的方法。
特別是,我們將首先實現距離的近似值。然後,我們將了解半正矢和文森蒂公式,它們提供了更高的準確性。
2. 等距矩形距離近似
讓我們從實現等距柱狀投影近似開始。詳細來說,由於該公式使用最少的數學運算,因此速度非常快:
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double lon1Rad = Math.toRadians(lon1);
double lon2Rad = Math.toRadians(lon2);
double x = (lon2Rad - lon1Rad) * Math.cos((lat1Rad + lat2Rad) / 2);
double y = (lat2Rad - lat1Rad);
double distance = Math.sqrt(x * x + y * y) * EARTH_RADIUS;
return distance;
}
上面的EARTH_RADIUS
是一個等於 6371 的常量,它非常接近地球半徑(以公里為單位)。
儘管看起來是一個簡單的公式,但等距柱狀近似在計算長距離時並不是很準確。事實上,它將地球視為一個完美的球體,並將該球體映射到一個矩形網格。
3. 使用半正矢公式計算距離
接下來,我們來看看半正矢公式。它再次將地球視為一個完美的球體。儘管如此,它在計算長距離之間的距離時更加準確。
此外,半正矢公式基於半正矢球面定律:
double haversine(double val) {
return Math.pow(Math.sin(val / 2), 2);
}
然後,使用這個輔助函數,我們可以實現計算距離的方法:
double calculateDistance(double startLat, double startLong, double endLat, double endLong) {
double dLat = Math.toRadians((endLat - startLat));
double dLong = Math.toRadians((endLong - startLong));
startLat = Math.toRadians(startLat);
endLat = Math.toRadians(endLat);
double a = haversine(dLat) + Math.cos(startLat) * Math.cos(endLat) * haversine(dLong);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c;
}
雖然它提高了計算的準確性,但它仍然認為地球是一個扁平的形狀。
4. 使用 Vincenty 公式計算距離
最後,如果我們想要最高精度,我們必須使用Vincenty 公式。具體來說, Vincenty 公式迭代計算距離,直到誤差達到可接受的值。此外,它還考慮了地球的橢圓形狀。
首先,該公式需要一些描述地球橢球模型的常數:
double SEMI_MAJOR_AXIS_MT = 6378137;
double SEMI_MINOR_AXIS_MT = 6356752.314245;
double FLATTENING = 1 / 298.257223563;
double ERROR_TOLERANCE = 1e-12;
顯然, ERROR_TOLERANCE
代表了我們願意接受的錯誤。此外,我們將在 Vincenty 公式中使用這些值:
double calculateDistance(double latitude1, double longitude1, double latitude2, double longitude2) {
double U1 = Math.atan((1 - FLATTENING) * Math.tan(Math.toRadians(latitude1)));
double U2 = Math.atan((1 - FLATTENING) * Math.tan(Math.toRadians(latitude2)));
double sinU1 = Math.sin(U1);
double cosU1 = Math.cos(U1);
double sinU2 = Math.sin(U2);
double cosU2 = Math.cos(U2);
double longitudeDifference = Math.toRadians(longitude2 - longitude1);
double previousLongitudeDifference;
double sinSigma, cosSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM;
do {
sinSigma = Math.sqrt(Math.pow(cosU2 * Math.sin(longitudeDifference), 2) +
Math.pow(cosU1 * sinU2 - sinU1 * cosU2 * Math.cos(longitudeDifference), 2));
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * Math.cos(longitudeDifference);
sigma = Math.atan2(sinSigma, cosSigma);
sinAlpha = cosU1 * cosU2 * Math.sin(longitudeDifference) / sinSigma;
cosSqAlpha = 1 - Math.pow(sinAlpha, 2);
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
if (Double.isNaN(cos2SigmaM)) {
cos2SigmaM = 0;
}
previousLongitudeDifference = longitudeDifference;
double C = FLATTENING / 16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha));
longitudeDifference = Math.toRadians(longitude2 - longitude1) + (1 - C) * FLATTENING * sinAlpha *
(sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * Math.pow(cos2SigmaM, 2))));
} while (Math.abs(longitudeDifference - previousLongitudeDifference) > ERROR_TOLERANCE);
double uSq = cosSqAlpha * (Math.pow(SEMI_MAJOR_AXIS_MT, 2) - Math.pow(SEMI_MINOR_AXIS_MT, 2)) / Math.pow(SEMI_MINOR_AXIS_MT, 2);
double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * Math.pow(cos2SigmaM, 2))
- B / 6 * cos2SigmaM * (-3 + 4 * Math.pow(sinSigma, 2)) * (-3 + 4 * Math.pow(cos2SigmaM, 2))));
double distanceMt = SEMI_MINOR_AXIS_MT * A * (sigma - deltaSigma);
return distanceMt / 1000;
}
該公式計算量很大。因此,當精度是目標時我們可能想要使用它。否則,我們將堅持使用半正矢公式。
5. 測試準確性
最後,我們可以測試上面所有方法的準確性:
double lat1 = 40.714268; // New York
double lon1 = -74.005974;
double lat2 = 34.0522; // Los Angeles
double lon2 = -118.2437;
double equirectangularDistance = EquirectangularApproximation.calculateDistance(lat1, lon1, lat2, lon2);
double haversineDistance = HaversineDistance.calculateDistance(lat1, lon1, lat2, lon2);
double vincentyDistance = VincentyDistance.calculateDistance(lat1, lon1, lat2, lon2);
double expectedDistance = 3944;
assertTrue(Math.abs(equirectangularDistance - expectedDistance) < 100);
assertTrue(Math.abs(haversineDistance - expectedDistance) < 10);
assertTrue(Math.abs(vincentyDistance - expectedDistance) < 0.5);
上面,我們計算了紐約和洛杉磯之間的距離,然後評估了以公里為單位的精度。
六,結論
在本文中,我們看到了在 Java 中計算兩個地理點之間距離的三種方法。我們從最不准確的等距矩形近似開始。然後我們研究了更準確的半正矢公式。最後,我們使用了最準確的文森蒂公式。
與往常一樣,示例中使用的代碼可以在 GitHub 上找到。
本作品係原創或者翻譯,採用《署名-非商業性使用-禁止演繹4.0國際》許可協議