分析与测试CRM和数据平台营销工具

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

这个月我一直在编程 PHPMySQL的 关于 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 – 第二个位置的经度变量。
  • $单位 – 默认是 英里. 这可以更新或传递为 公里.

Java:计算经纬度两点之间的距离

public static double getDistanceBetweenPointsNew(double latitude1, double longitude1, double latitude2, double longitude2, String unit) {
    double theta = longitude1 - longitude2;
    double distance = 60 * 1.1515 * (180/Math.PI) * Math.acos(
        Math.sin(latitude1 * (Math.PI/180)) * Math.sin(latitude2 * (Math.PI/180)) + 
        Math.cos(latitude1 * (Math.PI/180)) * Math.cos(latitude2 * (Math.PI/180)) * Math.cos(theta * (Math.PI/180))
    );
    if (unit.equals("miles")) {
        return Math.round(distance, 2);
    } else if (unit.equals("kilometers")) {
        return Math.round(distance * 1.609344, 2);
    } else {
        return 0;
    }
}

变量是:

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

Javascript:计算经纬度两点之间的距离

function getDistanceBetweenPoints(latitude1, longitude1, latitude2, longitude2, unit = 'miles') {
    let theta = longitude1 - longitude2;
    let distance = 60 * 1.1515 * (180/Math.PI) * Math.acos(
        Math.sin(latitude1 * (Math.PI/180)) * Math.sin(latitude2 * (Math.PI/180)) + 
        Math.cos(latitude1 * (Math.PI/180)) * Math.cos(latitude2 * (Math.PI/180)) * Math.cos(theta * (Math.PI/180))
    );
    if (unit == 'miles') {
        return Math.round(distance, 2);
    } else if (unit == 'kilometers') {
        return Math.round(distance * 1.609344, 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 以查找所有小于或等于变量 $distance(以英里为单位)到我在 $latitude 和 $longitude 的位置的记录:

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

$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.

Douglas Karr

Douglas Karr 是...的创始人 Martech Zone 以及公认的数字化转型专家。 Douglas 帮助创办了几家成功的 MarTech 初创公司,协助对超过 5 亿美元的 MarTech 收购和投资进行尽职调查,并继续推出他自己的平台和服务。 他是 Highbridge,一家数字化转型咨询公司。 道格拉斯还是一本傻瓜指南和商业领导力书籍的出版作者。

相关文章

78条评论

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

  2. 非常好的帖子——工作得非常好——我只需要更改持有 lat-long 的表的名称。 它的工作速度非常快。我有相当少量的经纬度(< 400),但我认为这会很好地扩展。 网站也不错——我刚刚将它添加到我的 del.icio.us 帐户中,并且会定期查看。

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

  4. 请记住,像这样的 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 次复杂距离计算,而不是一百万次。

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

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

        1. 道格

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

  5. 大家好,这是我的测试 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 的情况下做到这一点,它可以工作,但不能用它......

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

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

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

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

  10. 感谢您发布这篇有用的文章,  
    但出于某种原因我想问
    如何获取 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.” >>>>我可以从这里“取出”距离吗?
    再次感谢,
    蒂米小号

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

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

    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)); }

    谢谢你在前进

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

  13. 在选择中使用两次公式似乎更快(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.' 的距离<= '.$距离;

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

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

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

    $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) 作为距“寄存器”的距离“;

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

    我向我的网络服务器发出了一个获取请求
    53.47792 = $纬度
    -2.23389 = $经度
    和 20 = 我要检索的距离

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

    $results = DB::select( DB::raw(“SELECT *, ((acos(sin((“.$latitude.”pi()/180)) * sin((纬度pi()/180))+cos((“.$latitude.”pi()/180)) * cos((纬度pi()/180)) * cos(((“.$longitude.”- lng)圆周率()/180))))180/圆周率())601.1515*1.609344) 作为距标记的距离 >= “.$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 英里的行,但它会带来所有行。 请问我做错了什么

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

你觉得呢?

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