使用Haversine公式计算或查询经纬度点之间的大圆距离(PHP、Python、MySQL、MSSQL示例)

Haversine 公式 - 大圆距离 - PHP、Python、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)); 
}

变量是:

  • $纬度1 – 您的第一个位置的纬度变量。
  • $经度1 – 您的第一个位置的经度变量
  • $纬度2 – 第二个位置纬度的变量。
  • $经度2 – 第二个位置的经度变量。
  • $单位 – 默认是 英里. 这可以更新或传递为 公里.

Python:计算2个纬度和经度点之间的距离

无论如何,这是计算两点之间距离的 Python 公式(以及英里与公里的转换),四舍五入到小数点后两位。 感谢我的儿子比尔卡尔,他是一名数据科学家 开放洞察, 对于代码。

from numpy import sin, cos, arccos, pi, round

def rad2deg(radians):
    degrees = radians * 180 / pi
    return degrees

def deg2rad(degrees):
    radians = degrees * pi / 180
    return radians

def getDistanceBetweenPointsNew(latitude1, longitude1, latitude2, longitude2, unit = 'miles'):
    
    theta = longitude1 - longitude2
    
    distance = 60 * 1.1515 * rad2deg(
        arccos(
            (sin(deg2rad(latitude1)) * sin(deg2rad(latitude2))) + 
            (cos(deg2rad(latitude1)) * cos(deg2rad(latitude2)) * cos(deg2rad(theta)))
        )
    )
    
    if unit == 'miles':
        return round(distance, 2)
    if unit == 'kilometers':
        return round(distance * 1.609344, 2)

变量是:

  • 纬度1 – 您的第一个位置的变量 纬度.
  • 经度1 – 您的第一个位置的变量 经度
  • 纬度2 – 您的第二个位置的变量 纬度.
  • 经度2 – 您的第二个位置的变量 经度.
  • 单元 – 默认是 英里. 这可以更新或传递为 公里.

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

也可以使用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变量,用于传递点的经度。
  • $距离 –这是您要查找所有小于或等于的记录的距离。
  • –这是表格...您将要用表格名称替换它。
  • 纬度 –这是您的纬度领域。
  • 经度 –这是您经度的领域。

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

这是在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多个地点的零售商店,并且效果很好。

Microsoft SQL Server 地理距离:STDistance

如果您使用 Microsoft SQL Server,它们会提供自己的功能, 标准距离 用于使用 Geography 数据类型计算两点之间的距离。

DECLARE @g geography;  
DECLARE @h geography;  
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);  
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);  
SELECT @g.STDistance(@h);  

向 Manash Sahoo,副总裁兼架构师致敬 Highbridge.

77条评论

  1. 1

    非常感谢您的分享。 这是一个简单的复制和粘贴工作,效果很好。 你为我节省了很多时间。
    仅供移植到 C 的任何人使用:
    双度2rad(双度){返回度*(3.14159265358979323846/180.0); }

  2. 2

    非常好的帖子——工作得非常好——我只需要更改持有 lat-long 的表的名称。 它的工作速度非常快。我有相当少量的经纬度(< 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

    请记住,像这样的 select 语句计算量很大,因此速度很慢。 如果您有很多这样的查询,它可能会很快陷入困境。

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

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

    • 21
      • 22

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

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

        • 23

          道格

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

  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

    这很棒,但就像鸟儿飞翔一样。 尝试将谷歌地图 API 以某种方式(可能使用道路等)合并到这里会很棒。只是为了给出一个使用不同交通方式的想法。 我还没有在 PHP 中创建一个模拟退火函数,它能够为旅行商问题提供有效的解决方案。 但我认为我也许可以重用你的一些代码来做到这一点。

  18. 27
  19. 28
  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    2天的研究终于找到解决我问题的这个页面。 看起来我最好拿出我的 WolframAlpha 并复习我的数学。 从 WHERE 到 HAVING 的变化使我的脚本正常工作。 谢谢你

  25. 37
  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` WHERE distance >= “.$distance.” >>>>我可以从这里“取出”距离吗?
    再次感谢,
    蒂米小号

  41. 60

    好的,我尝试过的一切都不起作用。 我的意思是,我有什么作品,但距离很远。

    有人能看出这段代码有什么问题吗?

    if(isset($_POST['submitted'])){ $z = $_POST['zipcode']; $r = $_POST['半径']; echo “结果”.$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( z2.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); $已验证 = $row['已验证']; if($verified == '1'){ echo ""; echo "".$store.""; 回声 $dis 。 “ 几英里以外”; 回声“”; } else { echo "".$store.""; 回声 $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)) ); $距离 = acos($距离); $距离 = rad2deg($距离); $距离 = $距离 * 60 * 1.1515; switch($unit) { case 'Mi': break; 案例“公里”:$距离=$距离* 1.609344; } 返回(回合($距离,2)); }

    谢谢你在前进

  42. 61
  43. 62

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

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

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

  51. 71
  52. 72

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

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

  53. 73

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

    $qqq = “SELECT *,(((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) 作为距“寄存器”的距离“;

  54. 75
  55. 76

    你好,请我真的需要你的帮助。

    我向我的网络服务器发出了一个获取请求 http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $纬度
    -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) 作为距标记的距离 HAVING distance >= ".$distance ));

    [{“id”:1,”name”:”Frankie Johnnie & Luigo Too”,”address”:”939 W El Camino Real, Mountain View, CA”,”lat”:37.386337280273,”lng”:-122.08582305908, ”distance”:16079.294719663},{“id”:2,”name”:”Amici's East Coast Pizzeria”,”address”:”790 Castro St, Mountain View, CA”,”lat”:37.387138366699,”lng”: -122.08323669434,”distance”:16079.175940152},{“id”:3,”name”:”Kapp's Pizza Bar & Grill”,”address”:”191 Castro St, Mountain View, CA”,”lat”:37.393886566162, ”lng”:-122.07891845703,”distance”:16078.381373826},{“id”:4,”name”:”Round Table Pizza: Mountain View”,”address”:”570 N Shoreline Blvd, Mountain View, CA”, ”lat”:37.402652740479,”lng”:-122.07935333252,”distance”:16077.420540582},{“id”:5,”name”:”Tony & Alba's Pizza & Pasta”,”address”:”619 Escuela Ave, Mountain查看, CA”,”lat”:37.394012451172,”lng”:-122.09552764893,”distance”:16078.563225154},{“id”:6,”name”:”Oregano's Wood-Fired Pizza”,”address”:”4546 El Camino Real, Los Altos, CA”,”lat”:37.401725769043,”lng”:-122.11464691162,”distance”:16077.937560795},{“ id”:7,”name”:”酒吧和烧烤店”,”address”:”24 Whiteley Street, Manchester”,”lat”:53.485118865967,”lng”:-2.1828699111938,”distance”:8038.7620112314}]

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

  56. 77

    我正在寻找一个类似的查询,但加快了一点——简而言之,这是对每个坐标 2 英里内的所有坐标进行分组,然后计算每组中有多少个坐标,并只输出一个坐标最多的组——即使在坐标数最多的组中,您有多个组 - 只需从具有相同最大数的组中输出随机组 -

你觉得呢?

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