首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 計算兩地經緯度距離(Haversine 公式實現)

     編輯:PHP 相關知識

PHP 計算兩地經緯度距離(Haversine 公式實現)

基於經緯度計算球面距離是 LBS 應用核心功能。本文提供標準 Haversine 公式 PHP 實現,支援公里/英里雙單位,並封裝為高性能工具函數。

核心算法實現

<?php
/**
 * 計算兩點經緯度間的球面距離
 *
 * @param float $lat1 起點緯度
 * @param float $lng1 起點經度
 * @param float $lat2 終點緯度
 * @param float $lng2 終點經度
 * @param string $unit 單位:'km' 公里(默認) | 'mi' 英里 | 'm' 米
 * @return float 距離
 */
function haversineDistance(
    float $lat1, float $lng1,
    float $lat2, float $lng2,
    string $unit = 'km'
): float {
    // 地球平均半徑(單位:公里)
    $earthRadius = [
        'km' => 6371.0,
        'mi' => 3958.8,
        'm'  => 6371000.0,
    ];
    
    $R = $earthRadius[$unit] ?? $earthRadius['km'];
    
    // 角度轉弧度
    $lat1 = deg2rad($lat1);
    $lng1 = deg2rad($lng1);
    $lat2 = deg2rad($lat2);
    $lng2 = deg2rad($lng2);
    
    // 差值
    $dLat = $lat2 - $lat1;
    $dLng = $lng2 - $lng1;
    
    // Haversine 公式
    $a = sin($dLat / 2) ** 2 
       + cos($lat1) * cos($lat2) * sin($dLng / 2) ** 2;
    
    $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
    
    return $R * $c;
}

// 使用示例
// 北京天安門 -> 上海外灘
$distance = haversineDistance(39.9042, 116.4074, 31.2304, 121.4737);
echo "距離: " . round($distance, 2) . " km\n"; // 約 1067 km

$distanceM = haversineDistance(39.9042, 116.4074, 31.2304, 121.4737, 'm');
echo "距離: " . number_format($distanceM, 0) . " m\n"; // 約 1,067,000 m
?>

數學原理速覽

Haversine 公式計算球面上兩點間的大圓距離:

hav(θ) = sin²(θ/2) = (1 - cos θ) / 2

a = hav(Δφ) + cos φ1 ⋅ cos φ2 ⋅ hav(Δλ)
c = 2 ⋅ atan2(√a, √(1−a))
d = R ⋅ c

φ: 緯度, λ: 經度, R: 地球半徑

高性能批量計算優化

/**
 * 批量計算單點到多點距離(向量化優化)
 * 返回索引 => 距離 的數組
 */
function batchHaversine(
    float $lat1, float $lng1,
    array $targets, // [['lat' => , 'lng' =>], ...]
    string $unit = 'km'
): array {
    $R = match ($unit) {
        'km' => 6371.0,
        'mi' => 3958.8,
        'm'  => 6371000.0,
        default => 6371.0,
    };
    
    $lat1 = deg2rad($lat1);
    $lng1 = deg2rad($lng1);
    $cosLat1 = cos($lat1);
    
    $results = [];
    foreach ($targets as $i => $target) {
        $lat2 = deg2rad($target['lat']);
        $lng2 = deg2rad($target['lng']);
        
        $dLat = $lat2 - $lat1;
        $dLng = $lng2 - $lng1;
        
        $a = sin($dLat / 2) ** 2 
           + $cosLat1 * cos($lat2) * sin($dLng / 2) ** 2;
        
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
        $results[$i] = $R * $c;
    }
    return $results;
}

// 使用:查找附近 5km 內的門店
$stores = [
    ['id' => 1, 'lat' => 39.91, 'lng' => 116.41],
    ['id' => 2, 'lat' => 39.95, 'lng' => 116.38],
    // ...
];
$distances = batchHaversine(39.9042, 116.4074, $stores, 'km');
$nearby = array_filter($distances, fn($d) => $d <= 5);
?>

精度與替代方案對比

方法精度性能適用場景
Haversine~0.5% 誤差極快通用 LBS、距離排序、篩選
Vincenty 公式毫米級較慢測繪、導航、高精度測量
MySQL ST_Distance_Sphere同 HaversineDB 端計算數據庫層篩選、空間索引
PostGIS ST_Distance高精度DB 端複雜空間查詢、地理圍欄

MySQL 空間查詢示例(5.7+)

-- 查找 5km 內門店
SELECT id, name, 
    ST_Distance_Sphere(point(lng, lat), point(116.4074, 39.9042)) / 1000 AS distance_km
FROM stores
WHERE ST_Distance_Sphere(point(lng, lat), point(116.4074, 39.9042)) <= 5000
ORDER BY distance_km
LIMIT 20;

-- 空間索引加速 (MySQL 8.0)
ALTER TABLE stores ADD SPATIAL INDEX idx_location (location);
-- location 為 POINT 類型字段

實戰注意事項

  1. 坐標系統一致性:確保所有經緯度均為 WGS84(GPS 原始數據),國內地圖服務常用 GCJ02/BD09,需先轉換。
  2. 邊界條件:極地附近、經度跨 ±180° 時公式仍正確,但業務邏輯需注意。
  3. 性能陷阱:避免在大數據集上全表掃描計算距離,必須配合空間索引或先用矩形框粗篩。
  4. 緩存策略:熱門地點間距離可緩存,Key = `lat1,lng1:lat2,lng2`(對稱性)。

Haversine 公式以其簡潔高效成為 LBS 應用首選,配合數據庫空間索引可支撐千萬級 POI 查詢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved