計算兩組經緯度間的球面距離是 LBS 應用常見需求。本文提供基於 Haversine 公式 的 Java 實作,並說明公式原理、精度考量與替代方案。
假設地球為半徑 R = 6371 km 的完美球體,兩點 (lat1, lon1), (lat2, lon2) (弧度) 的球面距離:
a = sin²(Δlat/2) + cos(lat1)·cos(lat2)·sin²(Δlon/2)
c = 2·atan2(√a, √(1−a))
d = R·c
此公式在短距離數值穩定性優於球面餘弦定理,且避免反余弦在極小角度的精度損失。
public class GeoDistance {
private static final double EARTH_RADIUS_KM = 6371.0; // 平均半徑
private static final double DEG_TO_RAD = Math.PI / 180.0;
/**
* 計算兩點間球面距離 (公里)
* @param lon1 經度1
* @param lat1 緯度1
* @param lon2 經度2
* @param lat2 緯度2
* @return 距離 (km)
*/
public static double haversine(double lon1, double lat1, double lon2, double lat2) {
double dLat = (lat2 - lat1) * DEG_TO_RAD;
double dLon = (lon2 - lon1) * DEG_TO_RAD;
double lat1Rad = lat1 * DEG_TO_RAD;
double lat2Rad = lat2 * DEG_TO_RAD;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_KM * c;
}
/** 便利方法:回傳公尺 */
public static double haversineInMeters(double lon1, double lat1, double lon2, double lat2) {
return haversine(lon1, lat1, lon2, lat2) * 1000;
}
// 測試主程式
public static void main(String[] args) {
// 台北 101 到 台北車站 約 3.3 km
double dist = haversine(121.5645, 25.0338, 121.5173, 25.0478);
System.out.printf("距離: %.2f km (%.0f m)%n", dist, dist * 1000);
}
}
| 方法 | 精度 | 適用場景 | 備註 |
|---|---|---|---|
| Haversine | ~0.5% 誤差 (球體假設) | 一般 LBS、物流估算、半徑搜尋 | 速度快、無依賴 |
| Vincenty 公式 | 毫米級 (橢球體 WGS84) | 測量、精密導航 | 迭代收斂、較慢、極點可能不收斂 |
| GeographicLib | 奈米級 | 科學計算、GIS 核心 | Java 移植版 geographiclib-java |
| PostGIS / MySQL ST_Distance | 資料庫層級 | 空間查詢、就近搜尋 | 推薦下推至 DB 層 |
sin(lat), cos(lat)。double[] + 迴圈展開,或專案引入 Vector API (JEP 338/Incubator)。|Δlat|<threshold && |Δlon|<threshold) 粗篩,再精算。√(Δx²+Δy²) 計算經緯度 → 嚴重錯誤,極地附近誤差極大。Haversine 公式以 極低複雜度、零依賴、足夠精度 成為行動應用計算距離的首選。若需測量級精度,請改用 GeographicLib 或資料庫空間函式。記得根據業務場景選擇「球面距離」還是「路網距離」(需結合地圖 API)。