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

PHP 計算百度地圖 GPS 座標距離:Haversine 公式與百度墨卡托轉 WGS84

     編輯:PHP 綜合知識

地球曲面距離計算:Haversine 公式

給定兩點經緯度 (度),計算球面距離 (米)。

<?php
/**
 * 計算兩點球面距離
 * @param float $lat1 起點緯度
 * @param float $lng1 起點經度
 * @param float $lat2 終點緯度
 * @param float $lng2 終點經度
 * @return float 距離 (公尺)
 */
function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float {
    $earthRadius = 6371000; // 地球平均半徑 (公尺)

    // 角度轉弧度
    $lat1 = deg2rad($lat1);
    $lng1 = deg2rad($lng1);
    $lat2 = deg2rad($lat2);
    $lng2 = deg2rad($lng2);

    $dLat = $lat2 - $lat1;
    $dLng = $lng2 - $lng1;

    $a = sin($dLat / 2) ** 2 + cos($lat1) * cos($lat2) * sin($dLng / 2) ** 2;
    $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

    return $earthRadius * $c;
}

// 使用
$dist = haversineDistance(39.9042, 116.4074, 31.2304, 121.4737); // 北京 → 上海
echo round($dist / 1000, 1) . ' km'; // 約 1068.5 km
?>

百度座標系 (BD-09) 轉 WGS-84 (GPS 標準)

百度地圖使用 **BD-09** 加密座標,直接用 Haversine 會有偏移 (約幾百公尺)。需先轉換。

方法一:呼叫百度官方轉換 API (需 AK、有配額限制)

<?php
function bd09ToWgs84Api(float $lng, float $lat, string $ak): ?array {
    $url = "https://api.map.baidu.com/geoconv/v1/?coords={$lng},{$lat}&from=5&to=1&ak={$ak}";
    $json = @file_get_contents($url);
    if (!$json) return null;
    $data = json_decode($json, true);
    if (($data['status'] ?? -1) !== 0) return null;
    $result = $data['result'][0] ?? null;
    return $result ? [$result['x'], $result['y']] : null; // [lng, lat]
}
?>

方法二:離線演算法 (無需網路、適合批次處理)

參考開源實作 (如 `wgs84togcj02`、`gcj02tobd09` 反向)。

<?php
const PI = 3.14159265358979324;
const A = 6378245.0; // 長半軸
const EE = 0.00669342162296594323; // 扁率平方

function transformLat(float $lng, float $lat): float {
    $ret = -100.0 + 2.0 * $lng + 3.0 * $lat + 0.2 * $lat * $lat + 0.1 * $lng * $lat + 0.2 * sqrt(abs($lng));
    $ret += (20.0 * sin(6.0 * $lng * PI) + 20.0 * sin(2.0 * $lng * PI)) * 2.0 / 3.0;
    $ret += (20.0 * sin($lat * PI) + 40.0 * sin($lat / 3.0 * PI)) * 2.0 / 3.0;
    $ret += (160.0 * sin($lat / 12.0 * PI) + 320.0 * sin($lat / 30.0 * PI)) * 2.0 / 3.0;
    return $ret;
}
function transformLng(float $lng, float $lat): float {
    $ret = 300.0 + $lng + 2.0 * $lat + 0.1 * $lng * $lng + 0.1 * $lng * $lat + 0.1 * sqrt(abs($lng));
    $ret += (20.0 * sin(6.0 * $lng * PI) + 20.0 * sin(2.0 * $lng * PI)) * 2.0 / 3.0;
    $ret += (20.0 * sin($lng * PI) + 40.0 * sin($lng / 3.0 * PI)) * 2.0 / 3.0;
    $ret += (150.0 * sin($lng / 12.0 * PI) + 300.0 * sin($lng / 30.0 * PI)) * 2.0 / 3.0;
    return $ret;
}
function wgs84ToGcj02(float $lng, float $lat): array {
    if (outOfChina($lng, $lat)) return [$lng, $lat];
    $dLat = transformLat($lng - 105.0, $lat - 35.0);
    $dLng = transformLng($lng - 105.0, $lat - 35.0);
    $radLat = $lat / 180.0 * PI;
    $magic = sin($radLat);
    $magic = 1 - $EE * $magic * $magic;
    $sqrtMagic = sqrt($magic);
    $dLat = ($dLat * 180.0) / ((A * (1 - $EE)) / ($magic * $sqrtMagic) * PI);
    $dLng = ($dLng * 180.0) / (A / $sqrtMagic * cos($radLat) * PI);
    return [$lng + $dLng, $lat + $dLat];
}
function gcj02ToBd09(float $lng, float $lat): array {
    $z = sqrt($lng * $lng + $lat * $lat) + 0.00002 * sin($lat * PI);
    $theta = atan2($lat, $lng) + 0.000003 * cos($lng * PI);
    return [$z * cos($theta) + 0.0065, $z * sin($theta) + 0.006];
}
function bd09ToGcj02(float $lng, float $lat): array {
    $x = $lng - 0.0065;
    $y = $lat - 0.006;
    $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * PI);
    $theta = atan2($y, $x) - 0.000003 * cos($x * PI);
    return [$z * cos($theta), $z * sin($theta)];
}
function gcj02ToWgs84(float $lng, float $lat): array {
    if (outOfChina($lng, $lat)) return [$lng, $lat];
    [$gcjLng, $gcjLat] = wgs84ToGcj02($lng, $lat);
    return [$lng * 2 - $gcjLng, $lat * 2 - $gcjLat];
}
function bd09ToWgs84(float $lng, float $lat): array {
    [$gcjLng, $gcjLat] = bd09ToGcj02($lng, $lat);
    return gcj02ToWgs84($gcjLng, $gcjLat);
}
function outOfChina(float $lng, float $lat): bool {
    return $lng < 72.004 || $lng > 137.8347 || $lat < 0.8293 || $lat > 55.8271;
}

// 使用
[$wgsLng, $wgsLat] = bd09ToWgs84(116.4074, 39.9042); // 百度座標 → GPS
echo "WGS84: $wgsLng, $wgsLat";
?>

完整應用:計算兩個百度 POI 實際距離

<?php
function baiduPoiDistance(array $poi1, array $poi2, ?string $ak = null): float {
    // poi = ['lng' => ..., 'lat' => ...] (BD-09)
    if ($ak) {
        // 使用 API 轉換 (較準)
        $wgs1 = bd09ToWgs84Api($poi1['lng'], $poi1['lat'], $ak);
        $wgs2 = bd09ToWgs84Api($poi2['lng'], $poi2['lat'], $ak);
        if (!$wgs1 || !$wgs2) throw new RuntimeException('百度 API 轉換失敗');
    } else {
        // 離線轉換 (免費、批次快)
        $wgs1 = bd09ToWgs84($poi1['lng'], $poi1['lat']);
        $wgs2 = bd09ToWgs84($poi2['lng'], $poi2['lat']);
    }
    return haversineDistance($wgs1[1], $wgs1[0], $wgs2[1], $wgs2[0]);
}

// 範例:天安門 → 東方明珠
$poi1 = ['lng' => 116.3975, 'lat' => 39.9087]; // 百度座標
$poi2 = ['lng' => 121.4726, 'lat' => 31.2378];
echo round(baiduPoiDistance($poi1, $poi2) / 1000, 1) . ' km';
?>

座標系對照表

座標系用途中國偏移來源
WGS-84GPS 原始、Google Earth、國際標準無衛星定位
GCJ-02高德、騰訊、Google 中國、OSM 中國有 (加密)國測局標準
BD-09百度地圖有 (GCJ-02 再加密)百度自有

效能優化建議

  • 批次計算時**預先轉換所有座標至 WGS-84**,避免重複轉換。
  • 離線演算法單次約 0.05ms,百萬級可在秒級完成。
  • 若需極高精度 (米級),必須使用百度官方 API 或付費商業庫。
  • 資料庫儲存建議**同時存 WGS-84 與原始座標系**,查詢時直接用 WGS-84 索引。

MySQL 空間索引查詢 (進階)

ALTER TABLE pois ADD COLUMN location POINT NOT NULL;
UPDATE pois SET location = ST_PointFromText(CONCAT('POINT(', wgs_lng, ' ', wgs_lat, ')'), 4326);
ALTER TABLE pois ADD SPATIAL INDEX idx_location (location);

-- 查詢 5km 內的 POI
SELECT *, ST_Distance_Sphere(location, ST_PointFromText('POINT(116.3975 39.9087)', 4326)) AS dist_m
FROM pois
WHERE ST_Distance_Sphere(location, ST_PointFromText('POINT(116.3975 39.9087)', 4326)) <= 5000
ORDER BY dist_m ASC;

掌握座標系轉換與 Haversine 公式,配合 MySQL 空間索引,打造高效、精準的 LBS 服務。

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