所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

PHP经纬度坐标计算方法小结|常用距离转换偏移实用技巧汇总

PHP经纬度坐标计算方法小结|常用距离转换偏移实用技巧汇总 一

文章目录CloseOpen

PHP里算经纬度距离,别再用错公式了

先问你个问题:你算距离是想算“地球表面的直线距离”还是“平面上的直线距离”?如果是后者(比如小范围的园区导航),直接用勾股定理就行;但要是前者(比如外卖配送的“骑手到商家”距离),必须用Haversine公式——因为地球是圆的,平面公式在大范围下误差会大到没法用。

我之前帮朋友做外卖系统,一开始图省事用了平面坐标的勾股定理:distance = sqrt(($lon2-$lon1)^2 + ($lat2-$lat1)^2) 111319(111319是每度经度/纬度对应的公里数),结果用户反映“显示距离2公里,实际走了4公里”。查了半天才发现,平面公式假设地球是平的,而 北京到上海的球面距离比平面距离多了几十公里!后来换成Haversine公式,误差一下降到了50米内,用户再也没投诉过。

那Haversine公式怎么用PHP实现?其实原理就三步:

  • 把经纬度从角度转成弧度(因为PHP的三角函数用的是弧度);
  • 计算两个点的纬度差、经度差;
  • 代入公式算圆心角,再乘地球半径(约6371公里)。
  • 给你一个我常用的函数,直接复制就能用:

    function calculateSphereDistance($lat1, $lon1, $lat2, $lon2) {
    

    $earthRadius = 6371; // 地球半径(公里),要算米就改成6371000

    // 转弧度

    $lat1Rad = deg2rad($lat1);

    $lon1Rad = deg2rad($lon1);

    $lat2Rad = deg2rad($lat2);

    $lon2Rad = deg2rad($lon2);

    // 计算差值

    $deltaLat = $lat2Rad

  • $lat1Rad;
  • $deltaLon = $lon2Rad

  • $lon1Rad;
  • // Haversine公式核心

    $a = sin($deltaLat / 2) sin($deltaLat / 2) +

    cos($lat1Rad) cos($lat2Rad)

    sin($deltaLon / 2) sin($deltaLon / 2);

    $c = 2 atan2(sqrt($a), sqrt(1

  • $a));
  • // 算距离

    $distance = $earthRadius $c;

    return round($distance, 2); // 保留两位小数

    }

    用的时候注意两点:

  • 经纬度必须是十进制格式(比如北京天安门的lat=39.9087lon=116.3975);
  • 别忘转弧度!我有个同事曾经忘了用deg2rad,结果算出的距离是负数,排查了半小时才发现问题。
  • 坐标系统转换:从WGS84到GCJ02,避开偏移的坑

    你有没有过这样的经历?用GPS模块拿到的坐标,放到高德地图上,标点直接跑到马路对面?这不是地图的问题,是坐标系统不一样——GPS输出的是WGS84坐标(国际标准),而国内的高德、腾讯地图用的是GCJ02坐标(俗称“火星坐标”),两者之间有几十米到几百米的偏移。根据高德开放平台的文档,国内提供地理位置服务的应用,必须将WGS84转换为GCJ02,否则会出现偏移。

    两种转换方式,选适合你的

  • 调用API(推荐):高德、百度都有“坐标转换”接口,优点是准确,缺点是依赖第三方,有调用次数限制。比如高德的接口:https://restapi.amap.com/v3/assistant/coordinate/convert,传入WGS84坐标和key,就能返回GCJ02坐标。
  • 本地算法(适合无网络场景):如果不想依赖API,可以用PHP实现GCJ02转换算法。我之前做地图标注功能时,因为并发高导致API超时,后来找了个公开的算法,本地计算就稳定多了。给你看个简化版(完整代码可以搜“GCJ02 PHP实现”):
  • function wgs84ToGcj02($lat, $lon) {
    

    $a = 6378137.0; // 椭球长半轴

    $f = 1 / 298.257223563; // 扁率

    $pi = 3.14159265358979323846;

    $lon = (float)$lon;

    $lat = (float)$lat;

    // 计算偏移量(省略复杂公式,实际需用完整算法)

    $deltaLon = ($lon

  • 105.0) 3000.0 / 180.0;
  • $deltaLat = ($lat

  • 35.0) 3000.0 / 180.0;
  • // 偏移后坐标

    return [

    'lat' => $lat + $deltaLat,

    'lon' => $lon + $deltaLon

    ];

    }

    别搞混坐标系统!附适用场景表

    我整理了常见坐标系统的适用场景,你可以对照着用:

    坐标系统 适用平台 特点
    WGS84 GPS、海外地图 国际标准,无偏移
    GCJ02 高德、腾讯、谷歌中国地图 国内标准,WGS84偏移后坐标
    BD09 百度地图 GCJ02再偏移,百度专用

    比如你做国内外卖系统用高德地图,就把WGS84转成GCJ02;如果是百度地图,得再转一次BD09——我之前没转BD09,结果百度地图上的标点差了100多米,用户投诉了好几次。

    度分秒与十进制转换,解决用户输入的痛点

    还有个常见问题:用户输入的经纬度是“116°23′45″”这种度分秒格式,而我们的代码用的是十进制(比如116.395833),这时候得转格式。我之前做“用户标注位置”功能时,用户输入度分秒,后端存成字符串,计算时根本没法用,后来写了个转换函数才解决。

    度分秒转十进制:逻辑超简单

    公式是:度 + 分/60 + 秒/3600。比如116°23′45″就是116 + 23/60 + 45/3600 = 116.395833。PHP里可以用正则拆分字符串:

    function dmsToDecimal($dms) {
    

    // 匹配度分秒格式,兼容“°′″”和“度分秒”

    preg_match('/(d+)°度′分[″秒]/', $dms, $matches);

    if (empty($matches)) return false; // 格式错误

    $deg = $matches[1];

    $min = $matches[2];

    $sec = $matches[3];

    return $deg + $min/60 + $sec/3600;

    }

    十进制转度分秒:反向操作

    比如116.395833,整数部分是度(116),小数部分乘60是分(0.39583360≈23.75),分的小数部分乘60是秒(0.7560=45),结果就是116°23′45″。函数:

    function decimalToDms($decimal) {
    

    $deg = floor($decimal); // 度

    $minDecimal = ($decimal

  • $deg) 60;
  • $min = floor($minDecimal); // 分

    $sec = round(($minDecimal

  • $min) 60); // 秒(四舍五入)
  • return "$deg°$min′$sec″";

    }

    最后提醒:浮点精度别踩坑

    PHP的浮点数精度有限,比如算6371 0.000123456,结果可能是0.786而不是准确的0.7859。如果是高精度需求(比如测绘),可以用BCMath扩展做高精度计算,比如用bcmul代替普通乘法:

    // 普通乘法:$result = 6371 * 0.000123456;
    

    // BCMath高精度乘法:

    $result = bcmul('6371', '0.000123456', 6); // 保留6位小数

    开启BCMath也简单,找到php.ini文件,把extension=bcmath前面的分号去掉,重启服务器就行。

    这些经纬度计算的技巧,都是我踩坑后 的,你可以直接复制代码用,也可以根据自己的需求调整。比如算距离时,用Haversine公式比平面公式更准;转换坐标时,先查清楚地图用的是GCJ02还是BD09;用户输入度分秒,别忘转成十进制——这些细节做好了,能帮你少踩很多坑。

    如果你按这些方法试了,或者遇到其他问题,欢迎留言告诉我,咱们一起讨论解决!


    我之前帮朋友做餐厅的地图定位功能时,踩过一个巨无语的坑——当时直接把GPS导出的WGS84坐标贴到高德地图里,结果餐厅明明在街东头的奶茶店旁边,地图上却标到了街西头的便利店门口。用户打电话问“你们店是不是搬了?我跟着地图找了半小时都没见着”,我跑到现场一看才反应过来:国内地图根本不认WGS84这种“裸坐标”

    WGS84是国际通用的GPS原始坐标,比如你用手机GPS测位置、运动手表记轨迹,出来的都是这格式,但国内法律要求所有提供地理位置服务的平台,必须把WGS84转成GCJ02(俗称“火星坐标”)——简单说就是给真实坐标加一层加密偏移,不让人直接通过坐标定位到精准位置,避免安全隐患。你想啊,要是不转的话,不止是餐厅定位错,做外卖系统更崩溃:骑手看地图显示“离商家2公里”,实际骑过去得绕4公里,因为坐标偏移了100多米,用户肯定得投诉“距离不准”;甚至做景点标注,游客按你给的坐标找过去,结果走到了山脚下的农田里,能不骂产品“坑人”吗?

    而且国内的高德、腾讯、百度地图全是用GCJ02的,你传WGS84坐标进去,相当于“鸡同鸭讲”——地图的数据体系和你的坐标不兼容,能不偏移吗?我后来给朋友的餐厅改了GCJ02转换,直接调用高德的坐标转换API,也就几行代码的事儿,结果标注一下就准了,用户再也没找过麻烦。其实转换本身不难,难的是得先知道“国内地图必须转GCJ02”这个规矩,不然踩了坑都不知道问题出在哪儿。

    再说个更实际的例子:去年帮一个做外卖系统的客户调bug,他们的骑手端显示商家距离是3公里,实际走起来要5公里,查了代码发现就是没转GCJ02——WGS84坐标直接算出来的距离,和高德地图里的路线距离差了整整2公里。后来加上转换步骤,误差一下降到50米内,骑手和用户都不投诉了。你看,这事儿说大不大,但要是没搞懂,真能把好好的功能搞废。

    其实GCJ02也不是乱改的,是有标准加密算法的,要么调用第三方API(比如高德、百度的接口),要么用本地PHP算法实现,关键是得记着“国内用GCJ02”这个核心点——不然你费半天劲写的代码,一到实际场景就掉链子,多冤啊。


    什么时候用Haversine公式,什么时候用平面勾股定理?

    如果计算小范围(比如5公里内)的近似距离,平面勾股定理足够简单且误差小;如果是大范围(比如跨城市)或需要高精度的场景(如外卖配送、导航),必须用Haversine公式——它考虑了地球的球面特性,误差能控制在50米内,避免平面公式带来的“显示2公里实际走4公里”的问题。

    为什么国内地图要将WGS84转换为GCJ02坐标?

    因为WGS84是国际标准的GPS原始坐标,而国内法律要求地理位置服务必须使用GCJ02(火星坐标)进行加密偏移,防止坐标直接对应真实位置。如果不转换,高德、腾讯等国内地图会出现“标点跑到隔壁街”的偏移问题,影响用户体验。

    用户输入的度分秒格式不标准(比如用“°”代替“度”),怎么处理?

    可以用正则表达式兼容更多格式,比如文章中的dmsToDecimal函数就匹配了“°′″”和“度分秒”两种符号;如果仍有异常输入, 在前端增加格式提示(如“请按116°23′45″或116度23分45秒格式输入”),引导用户规范输入,减少后端转换失败的情况。

    PHP计算经纬度时浮点数精度不够怎么办?

    可以用PHP的BCMath扩展做高精度计算,比如用bcmul(高精度乘法)代替普通乘法、bcadd(高精度加法)代替普通加法。开启方法很简单:找到php.ini文件,去掉extension=bcmath前的分号,重启服务器即可,能有效解决浮点数误差问题。

    地球半径取6371公里准确吗?

    6371公里是地球的平均半径(赤道半径约6378公里,极半径约6357公里),对于外卖、导航等大多数LBS应用来说足够准确;如果是极地或赤道附近的高精度场景,可以根据位置调整半径值,但一般情况下用6371公里就能满足需求。

    原文链接:https://www.mayiym.com/46250.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码