PHP和SQL:使用Haversine公式计算或查询纬度和经度之间的大圆距离

Haversine公式-使用PHP或MySQL计算大圆距

本月,我已经在PHP和MySQL方面进行了相当多的GIS编程。 在网上窥探,我实际上很难找到一些 地理计算 查找两个位置之间的距离,所以我想在这里分享。

飞行地图欧洲,具有很大的圆周距离

计算两点之间的距离的简单方法是使用勾股定律公式来计算三角形的斜边(A²+B²=C²)。 这就是所谓的 欧氏距离.

这是一个有趣的开始,但不适用于地理,因为纬度和经度之间的距离为 不等距离 分开。 当您靠近赤道时,纬度线会越来越远。 如果使用某种简单的三角剖分方程,由于地球的曲率,它可能在一个位置精确地测量距离,而在另一个位置则非常错误。

大圆距离

在地球上长途旅行的路线被称为 大圆距离。 也就是说,球体上两个点之间的最短距离不同于平面图中的点。 再加上纬度和经度线不是等距的事实,您将很难进行计算。

这是有关Great Circles工作原理的精彩视频说明。

Haversine公式

使用地球曲率的距离包含在 Haversine公式,它使用三角函数允许地球弯曲。 当您发现地球上两个地方之间的距离(乌鸦飞翔)时,一条直线实际上是一条弧线。

这适用于空中飞行–您是否曾经查看过实际的飞行地图并注意到它们是拱形的? 这是因为在两点之间的拱形中飞行比直接飞到该位置要短。

PHP:计算纬度和经度两个点之间的距离

无论如何,这是用于计算两点之间的距离(以及英里与公里转换)的PHP公式,四舍五入到小数点后两位。

function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'miles') {
  $theta = $longitude1 - $longitude2; 
  $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta))); 
  $distance = acos($distance); 
  $distance = rad2deg($distance); 
  $distance = $distance * 60 * 1.1515; 
  switch($unit) { 
    case 'miles': 
      break; 
    case 'kilometers' : 
      $distance = $distance * 1.609344; 
  } 
  return (round($distance,2)); 
}

SQL:通过使用纬度和经度计算距离(以英里为单位)来检索范围内的所有记录

也可以使用SQL进行计算以查找特定距离内的所有记录。 在此示例中,我将在MySQL中查询MyTable,以查找到我在$ latitude和$ longitude位置处小于或等于变量$ distance(以Miles为单位)的所有记录:

用于检索特定范围内的所有记录的查询 距离 通过计算纬度和经度两个点之间的距离(英里)为:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`)*pi()/180)))) * 180/pi()) * 60 * 1.1515) as distance FROM `table` WHERE distance <= ".$distance."

您需要对此进行自定义:

  • $经度 –这是一个PHP变量,我在其中传递点的经度。
  • $纬度 –这是一个PHP变量,我在其中传递点的经度。
  • $距离 –这是您要查找所有小于或等于的记录的距离。
  • –这是表格...您将要用表格名称替换它。
  • 纬度 –这是您的纬度领域。
  • 经度 –这是您经度的领域。

SQL:通过使用纬度和经度计算公里中的距离来检索范围内的所有记录

这是在MySQL中使用千米的SQL查询:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as distance FROM `table` WHERE distance <= ".$distance."

您需要对此进行自定义:

  • $经度 –这是一个PHP变量,我在其中传递点的经度。
  • $纬度 –这是一个PHP变量,我在其中传递点的经度。
  • $距离 –这是您要查找所有小于或等于的记录的距离。
  • –这是表格...您将要用表格名称替换它。
  • 纬度 –这是您的纬度领域。
  • 经度 –这是您经度的领域。

我在企业映射平台中使用了此代码,并将其用于在北美拥有1,000多个地点的零售商店,并且效果很好。

76条评论

  1. 1

    非常感谢您的分享。 这是一项简单的复制和粘贴工作,效果很好。 你为我节省了很多时间。
    供任何移植到C的人参考:
    double deg2rad(double deg){return deg *(3.14159265358979323846 / 180.0); }

  2. 2

    非常好的发布–做得很好–我只需要更改持有经纬度的表的名称即可。 它的运行速度非常快。.我有相当数量的经纬度(<400),但我认为这样可以很好地扩展。 也是不错的网站-我刚刚将其添加到我的del.icio.us帐户中,并将定期检查。

  3. 4
  4. 5
  5. 8
  6. 10
  7. 11
  8. 12
  9. 14
  10. 15
  11. 16

    我还发现WHERE不适用于我。 将其更改为HAVING,一切正常。 起初,我没有阅读注释,而是使用嵌套选择将其重写。 两者都可以正常工作。

  12. 17
  13. 18

    非常有用,非常感谢! 我在使用新的“ HAVING”(而不是“ WHERE”)时遇到了一些问题,但是一旦我在这里阅读了评论(在沮丧地磨牙= P大约半小时后),我就可以很好地工作了。 谢谢^ _ ^

  14. 19
  15. 20

    请记住,这样的选择语句将在计算上非常繁琐,因此速度很慢。 如果您有很多这样的查询,它可能很快就会使您陷入困境。

    一种不太强烈的方法是使用由计算距离定义的 SQUARE 区域运行第一个(粗略)选择,即“从表名中选择 * 其中纬度在 lat1 和 lat2 之间,经度在 lon1 和 lon2 之间”。 lat1 = 目标纬度 – latdiff,lat2 = 目标纬度 + latdiff,与 lon 类似。 latdiff〜=距离/ 111(对于km),或者距离/ 69对于英里,因为1个纬度为〜111 km(由于地球略呈椭圆形,因此略有变化,但足以满足此目的)。 londiff = distance / (abs(cos(deg2rad(latitude))*111)) — 或 69 英里(您实际上可以采用稍大的正方形以考虑变化)。 然后获取结果并将其输入到径向选择中。 只是不要忘记考虑越界坐标——即可接受的经度范围是 -180 到 +180,可接受的纬度范围是 -90 到 +90——以防你的 latdiff 或 londiff 超出这个范围. 请注意,在大多数情况下,这可能不适用,因为它仅影响从极地到极地穿过太平洋的一条线上的计算,尽管它确实与楚科奇的一部分和阿拉斯加的一部分相交。

    通过此操作,我们可以大大减少进行此计算所需的分数。 如果数据库中有一百万个全局点,并且您想在100公里范围内进行大致均匀的分布,则您的第一次(快速)搜索区域为10000平方公里,并且可能会产生约20个结果(基于整个区域的均匀分布)表面积约500M平方公里),这意味着您需要为此查询运行20次复杂距离计算,而不是一百万次。

    • 21
      • 22

        很棒的建议! 我实际上与一位开发人员一起工作,该开发人员编写了一个函数,该函数可以拉动内部正方形,然后是一个递归函数,该函数在周边制作“正方形”以包含和排除剩余的点。 结果是一个非常快的结果–他可以在几微秒内评估数百万个点。

        我上面的方法肯定是“粗鲁的”但有能力的。 再次感谢!

        • 23

          道格

          我一直在尝试使用mysql和php来评估经纬度是否在多边形内。 您是否知道您的开发者朋友是否发布了有关如何完成此任务的任何示例。 或者你知道什么好的例子。 提前致谢。

  16. 24

    大家好,这是我的测试SQL语句:

    SELECT DISTINCT area_id, (
    (
    (
    acos( sin( ( 13.65 * pi( ) /180 ) ) * sin( (
    `lat_dec` * pi( ) /180 ) ) + cos( ( 13.65 * pi( ) /180 ) ) * cos( (
    `lat_dec` * pi( ) /180 )
    ) * cos( (
    ( 51.02 - `lon_dec` ) * pi( ) /180 )
    )
    )
    ) *180 / pi( )
    ) *60 * 1.1515 * 1.609344
    ) AS distance
    FROM `post_codes` WHERE distance <= 50

    并且Mysql告诉我距离不作为一列存在,我可以使用order by,我可以在没有WHERE的情况下进行操作,并且它可以工作,但不能使用…

  17. 26

    这很好,但是就像鸟儿飞翔一样。 尝试以某种方式(可能使用道路等)将google maps API合并到其中,以给出使用不同形式的交通工具的想法是非常好的。 我还没有在PHP中创建一个模拟的退火函数,该函数能够为旅行商问题提供有效的解决方案。 但是我认为我也许可以重用您的某些代码。

  18. 27
  19. 28

    好文章! 我找到了很多描述如何计算两点之间距离的文章,但我确实在寻找SQL代码段。

  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    经过2天的研究,终于找到了解决我的问题的页面。 看来我最好把我的WolframAlpha弄清楚,然后重新计算数学。 从WHERE到HAVING的更改使我的脚本正常运行。 谢谢

  25. 37
    • 38

      谢谢乔治。 我一直在找不到列“距离”。 一旦我将WHERE更改为HAVERING,它就会像魅力一样发挥作用!

  26. 39

    我希望这是我在此找到的第一页。 在尝试了许多不同的命令之后,这是唯一可以正常工作的命令,并且只需进行最小的更改即可适合我自己的数据库。
    多谢了!

  27. 40

    我希望这是我在此找到的第一页。 在尝试了许多不同的命令之后,这是唯一可以正常工作的命令,并且只需进行最小的更改即可适合我自己的数据库。
    多谢了!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47
  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    感谢您发布这篇有用的文章,  
    但出于某些原因我想问
    如何获取mysql db内部坐标与用户插入php的坐标之间的距离?
    更清楚地描述:
    1.用户必须插入[id]从数据库和用户本身的坐标中选择指定数据
    2. php文件使用[id]获取目标数据(坐标),然后计算用户与目标点之间的距离

    还是可以简单地从下面的代码获取距离?

    $ qry =“ SELECT *,((((acos(sin((“。$ latitude。” * pi()/ 180))* sin((`Latitude` * pi()/ 180))+ cos((”。 $ latitude。” * pi()/ 180))* cos((`Latitude` * pi()/ 180))* cos((((“。$ longitude。”-`Longitude`)* pi()/ 180) )))* 180 / pi())* 60 * 1.1515 * 1.609344)作为距`MyTable`的距离,其中distance> =“。$ distance。” >>>>我能“拿走”到这里的距离吗?
    再次感谢,
    蒂米S

    • 59
  41. 60

    好的,我尝试过的所有方法均无效。 我的意思是,我所拥有的作品,但是相距遥远。

    有人可以看到这段代码有什么问题吗?

    if(isset($ _ POST ['submitted'])){$ z = $ _POST ['zipcode']; $ r = $ _POST ['radius']; 回显“。$ z的结果”; $ sql = mysql_query(“ SELECT DISTINCT m.zipcode,m.MktName,m.LocAddSt,m.LocAddCity,m.LocAddState,m.x1,m.y1,m.verified,z1.lat,z2.lon,z1。 city,z1.state FROM mrk m,zip z1,zip z2 WHERE m.zipcode = z1.zipcode AND z2.zipcode = $ z AND(3963 * acos(truncate(sin(zin。z / 2.lat / 57.2958)* sin(m。 y1 / 57.2958)+ cos(z2.lat / 57.2958)* cos(m.y1 / 57.2958)* cos(m.x1 / 57.2958 – z2.lon / 57.2958),8))))<= $ r“)或死亡(mysql_error()); while($ row = mysql_fetch_array($ sql)){$ store1 = $ row ['MktName']。“”; $ store = $ row ['LocAddSt']。””; $ store。= $ row ['LocAddCity']。”,“。$ row ['LocAddState']。”。 “。$ row ['zipcode']; $ latitude1 = $ row ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew($ latitude1,$ longitude1,$ latitude2,$ longitude2,$ unit ='Mi'); // $ dis = distance($ lat1,$ lon1,$ lat2,$ lon2); $ verified = $ row ['verified']; if($ verified =='1'){回声“”; 回显“”。$ store。””; echo $ dis。 “ 几英里以外”; 回声“”; } else {echo“”。$ store。””; echo $ dis。 “ 几英里以外”; 回声“”; }}}

    我的functions.php代码
    函数getnew($ latitude1,$ longitude1,$ latitude2,$ longitude2,$ unit ='Mi'){$ theta = $ longitude1 – $ longitude2; $ distance =(sin(deg2rad($ latitude1))* sin(deg2rad($ latitude2)))+(cos(deg2rad($ latitude1))* cos(deg2rad($ latitude2))* cos(deg2rad($ theta)) ); $ distance = acos($ distance); $ distance = rad2deg($ distance); $距离= $距离* 60 * 1.1515; switch($ unit){case'Mi':break; 情况'Km':$ distance = $ distance * 1.609344; } return(round($ distance,2)); }

    谢谢你在前进

  42. 61
  43. 62

    嘿道格拉斯,很棒的文章。 我发现您对地理概念和代码的解释非常有趣。 我唯一的建议是对代码进行空格和缩进显示(例如,像Stackoverflow)。 我知道您想节省空间,但是传统的代码间距/缩进将使我(作为程序员)更容易阅读和剖析。 无论如何,这是一件小事。 继续努力。

  44. 64
  45. 65
  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    似乎在select和其中使用两次公式的速度更快(mysql 5.9):
    $ formula =“((((acos(sin((”。$ latitude。“ * pi()/ 180))* sin((`Latitude` * pi()/ 180))+ cos((”。$ latitude。 ” * pi()/ 180))* cos((`Latitude` * pi()/ 180))* cos(((“。$ longitude.-`Longitude`)* pi()/ 180))))) * 180 / pi())* 60 * 1.1515 * 1.609344)”;
    $ sql ='SELECT *,'。$ formula。' 作为距表格WHERE'.. $ formula。'的距离。 <='。$ distance;

  51. 71
  52. 72

    非常感谢本文的剪裁。它非常有帮助。
    PHP最初是作为一个简单的脚本平台创建的,称为“个人主页”。 如今,PHP(超文本预处理器的缩写)是Microsoft的Active Server Pages(ASP)技术的替代方法。

    PHP是一种开放源代码服务器端语言,用于创建动态网页。 可以将其嵌入HTML。 PHP通常与Linux / UNIX Web服务器上的MySQL数据库结合使用。 它可能是最流行的脚本语言。

  53. 73

    我发现上述解决方案无法正常工作。
    我需要更改为:

    $ qqq =“选择*,((((acos(sin((“。$ latitude。” * pi()/ 180))* sin((`latt * pi()/ 180))+ cos((”。 $ latitude。“ * pi()/ 180))* cos((`latt ** pi()/ 180))* cos((((。。$ longitude。“-`longt`)* pi()/ 180) )))* 180 / pi())* 60 * 1.1515)作为距`register`的距离;

  54. 75
  55. 76

    您好,请您在这方面确实需要您的帮助。

    我向我的网络服务器发出了获取请求 http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitude
    -2.23389 = $经度
    和20 =我要检索的距离

    但是使用公式,它将检索我的数据库中的所有行

    $ results = DB :: select(DB :: raw(“ SELECT *,((((acos(sin((“。$ latitude。” * pi()/ 180))* sin((lat * pi()/ 180 ))+ cos((“。$ latitude。” * pi()/ 180))* cos((lat * pi()/ 180))* cos(((((。$ longitude。”-lng)* pi( )/ 180))))* 180 / pi())* 60 * 1.1515 * 1.609344)作为距离标记的距离,距离> =“。$ distance));

    [{{“ id”:1,“ name”:“ Frankie Johnnie&Luigo Too”,“ address”:“ 939 W El Camino Real,CA,Mountain”,“ lat”:37.386337280273,“ lng”:-122.08582305908, “距离”:16079.294719663},{“ id”:2,“名称”:“阿米奇的东海岸比萨店”,“地址”:“加利福尼亚州山景城Castro St 790”,“纬度”:37.387138366699,“ lng”: -122.08323669434,“距离”:16079.175940152},{“ id”:3,“名称”:“ Kapp's Pizza Bar&Grill”,“地址”:“ 191 Castro St,Mountain View,CA”,“ lat”:37.393886566162, “ lng”:-122.07891845703,“距离”:16078.381373826},{“ id”:4,“ name”:“ Round Table Pizza:Mountain View”,“ address”:“ 570 N Shoreline Blvd,CA,Mountain View”, “ lat”:37.402652740479,“ lng”:-122.07935333252,“ distance”:16077.420540582},{“ id”:5,“ name”:“ Tony&Alba's Pizza&Pasta”,“ address”:“ 619 Escuela Ave,Mountain View,CA”,“ lat”:37.394012451172,“ lng”:-122.09552764893,“ distance”:16078.563225154},{“ id”:6,“ name”:“ Oregano's Wood-fired Pizza”,“ address”:“ 4546” El Camino Real,加利福尼亚州洛斯阿尔托斯市”,“纬度”:37.401725769043,“ lng”:-122.11464691162,“距离”:16077.937560795},{“ id”:7,“ name”:“ The bars and grills”,“ address”:“ Manchester Whiteley Street 24”,“ lat”:53.485118865967,“ lng”:-2.1828699111938,“ distance”:8038.7620112314}]]

    我只想检索20英里的行,但它会带走所有行。 请问我在做什么错

你觉得呢?

本网站使用Akismet来减少垃圾邮件。 了解您的数据如何处理.