基於球面三角學的 Haversine 公式 計算兩點間大圓距離,適合 GPS 定位、LBS 服務、地理圍欄等場景。
public final class GeoUtils {
private static final double EARTH_RADIUS_KM = 6378.137; // 赤道半徑 (km)
private static final double METERS_PER_DEGREE_LAT = 111_000; // 近似值
private GeoUtils() {} // 工具類禁止實例化
/**
* 計算兩點間距離 (單位:公尺)
*/
public static double distanceInMeters(double lat1, double lon1, double lat2, double lon2) {
double radLat1 = Math.toRadians(lat1);
double radLat2 = Math.toRadians(lat2);
double dLat = radLat1 - radLat2;
double dLon = Math.toRadians(lon1) - Math.toRadians(lon2);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(radLat1) * Math.cos(radLat2)
* Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.asin(Math.sqrt(a));
return c * EARTH_RADIUS_KM * 1000; // 轉公尺
}
/**
* 依中心點與半徑計算外接矩形範圍 (經緯度邊界)
* @return double[] {minLon, minLat, maxLon, maxLat}
*/
public static double[] boundingBox(double centerLon, double centerLat, double radiusMeters) {
if (centerLon == 0 && centerLat == 0) {
// 極點特殊處理:經度跨度極大,實際應用建議拋出異常或回傳全球範圍
return new double[] {-180, -90, 180, 90};
}
// 緯度跨度固定
double latDelta = radiusMeters / METERS_PER_DEGREE_LAT;
// 經度跨度隨緯度變化:cos(lat) 修正
double lonDelta = radiusMeters / (Math.abs(Math.cos(Math.toRadians(centerLat))) * METERS_PER_DEGREE_LAT);
return new double[] {
centerLon - lonDelta, centerLat - latDelta,
centerLon + lonDelta, centerLat + latDelta
};
}
/** 度轉弧度 */
private static double toRad(double deg) {
return deg * Math.PI / 180.0;
}
}
搭配 MySQL 空間索引或 PostgreSQL PostGIS:
-- MySQL 5.7+ 空間索引查詢
SELECT * FROM shops
WHERE ST_Distance_Sphere(
point(lng, lat),
point(121.5654, 25.0330)
) <= 1000; -- 1 公里內
-- 矩形範圍預過濾 (效能優化)
SELECT * FROM shops
WHERE lng BETWEEN 121.55 AND 121.58
AND lat BETWEEN 25.02 AND 25.04
AND ST_Distance_Sphere(point(lng, lat), point(121.5654, 25.0330)) <= 1000;
| 方法 | 精度 | 效能 | 適用場景 |
|---|---|---|---|
| Haversine | 高 (球面模型) | 中 | 一般 LBS、導航、圍欄 |
| Vincenty 公式 | 極高 (橢球體模型) | 低 | 測量、精密導航 |
| 平面近似 (歐氏距離) | 低 (小範圍可接受) | 極高 | 極小範圍、嵌入式裝置 |
| MySQL ST_Distance_Sphere | 高 (資料庫層) | 高 (索引支援) | 大量資料空間查詢 |
lng, lat (x, y) vs lat, lng (y, x) —— API 文件必須確認DECIMAL(10,7) 或 DOUBLE