【问题标题】:Selecting Latitude and Longitude points within a radius选择半径内的纬度和经度点
【发布时间】:2015-05-07 04:53:22
【问题描述】:

我正在使用论坛在给定位置和半径的数据库中选择纬度/经度点,但是我无法正常工作。只有当我使用 6371 公里(世界的大小)的半径时,我才能让查询工作,我认为论坛存在问题,但我似乎无法弄清楚在哪里......

这是我的 sql 查询

   $latitude = $_POST["latitude"]; // latitude of centre of bounding circle in degrees
   $longitude = $_POST["longitude"];; // longitude of centre of bounding circle in degrees
   $rad = $_POST["radius"]; // radius of bounding circle in kilometers
   $R = 6371;// earth's mean radius, km
   $maxLat = $latitude + rad2deg($rad/$R);
   $minLat = $latitude - rad2deg($rad/$R);
    // compensate for degrees longitude getting smaller with increasing latitude
   $maxLon = $longitude + rad2deg($rad/$R/cos(deg2rad($latitude)));
   $minLon = $longitude - rad2deg($rad/$R/cos(deg2rad($latitude)));



   $con = mysqli_connect("localhost","root","Winter#05");

   if (!$con)
   {
   die('Could not connect: ' . mysql_error());
   }
   mysqli_select_db($con, "appdatabase");


   $result = mysqli_query($con,"select id, PostTitle, SubmitDate, PostVotes, ImagePath, comments, latitude, longitude, acos(sin($latitude)*sin(radians(latitude)) + cos($latitude)*cos(radians(latitude))*cos(radians(longitude)-($longitude))) * $R as D 
   from ( select id, PostTitle, SubmitDate, PostVotes, comments, ImagePath, latitude, longitude 
   from posts where latitude > $minLat and latitude < $maxLat and longitude > $minLon and longitude < $maxLon ) as first_cut 
   where acos(sin($latitude)*sin(radians(latitude)) + cos($latitude)*cos(radians(latitude))*cos(radians(longitude) - ($longitude))) * $R < $rad order by D") or die('Errant query:');

   while($row = mysqli_fetch_assoc($result))
   {
        $output[]=$row;
   }


   print(json_encode($output));

   mysqli_close($con);

【问题讨论】:

  • 您的 rad2deg 调用没有任何意义。你基本上是在做rad2deg(6371/6371)。那只是计算两个长度的比例。您需要将该比例转换为弧度。
  • 这个表达式似乎是错误的(它出现的两个地方):rad2deg($rad/$R/cos(deg2rad($latitude)))。应该是rad2deg($rad/($R*cos(deg2rad($latitude))))。不过,您的其他 rad2deg() 电话在我看来是正确的。
  • @MarB 并不总是这样 (6371/6371),我只是将半径设置为最大的,以便进行调试。但是,如果我想减少我选择的点的半径,那么我需要调整 $rad
  • @JohnBollinger 嗯,试过了。似乎没有工作。没有给我任何结果。

标签: php mysql sql mysqli


【解决方案1】:

一个问题是$latitude$longitude 似乎正在以 为单位合并到SQL 文本中。该公式(在 SQL 查询中实现)看起来期望这些位置的值以 弧度 为单位。


如果我必须阅读/维护这段代码,如果它是这样编写的,那么理解查询会更容易

SELECT p.id
     , p.PostTitle
     , p.SubmitDate
     , p.PostVotes
     , p.ImagePath
     , p.comments
     , p.latitude
     , p.longitude
     , ACOS( SIN(       (  $latitude ))
           * SIN(RADIANS( p.latitude ))
           + COS(       (  $latitude ))
           * COS(RADIANS( p.latitude ))
           * COS(RADIANS( p.longitude ) -        ( $longitude ))
           ) * $R
       AS D
  FROM posts p
 WHERE p.latitude  > $minLat
   AND p.latitude  < $maxLat 
   AND p.longitude > $minLon
   AND p.longitude < $maxLon 
HAVING D < $rad
 ORDER BY D

大圆距离公式看起来不错,但我想知道为什么 $latitude$longitude 以弧度而不是度为单位提供。

然后我查看 PHP 代码,你瞧,它们实际上是以度为单位的,我发现缺少到弧度的转换。

返回距离“D”的表达式中缺少的只是$latitude$longitude 周围的RADIANS 函数

所以这个:

     , ACOS( SIN(       (  $latitude ))
           * SIN(RADIANS( p.latitude ))
           + COS(       (  $latitude ))
           * COS(RADIANS( p.latitude ))
           * COS(RADIANS( p.longitude ) -        ( $longitude ))
           ) * $R
       AS D

被替换为

     , ACOS( SIN(RADIANS(  $latitude ))
           * SIN(RADIANS( p.latitude ))
           + COS(RADIANS(  $latitude ))
           * COS(RADIANS( p.latitude ))
           * COS(RADIANS( p.longitude ) - RADIANS( $longitude ))
           ) * $R
       AS D

如果我要维护它,我也会将它转换为带有 bind placeholdersprepared statement。至少,我会正确地转义任何被合并到 SQL 文本中的潜在不安全值...mysqli_real_escape_string

例如

$sql .= "  + COS(RADIANS( " . mysqli_real_escape_string($con,$latitude) . " ))"

我怀疑$minLat$maxLat$minLon$maxLon 的计算。我一定会测试这些。为了测试没有这些的查询,我可以注释掉整个 WHERE 子句,然后计算表中 每个 翻转行的距离 D,然后通过 @987654338 过滤它们@ 子句。

我们确实希望在 WHERE 子句中包含谓词(如果正确完成)将提供一个“边界框”,它将限制我们需要通过大圆计算进行曲折的行数。

消除内联视图应该会提高性能,因为 MySQL 不会有具体化派生表的开销。

【讨论】:

  • 感谢您的回复,非常有见地。尝试您的方法后,我仍然没有得到任何结果。虽然我认为这部分与最小/最大纬度计算有关。我将回应结果,看看它会把我带到哪里。我会让你知道我的发现
  • @ErrolGreen:作为测试,请尝试删除 WHERE 子句限制(在最小/最大纬度/经度上,并仅使用 HAVING 条件和大圆计算。您还可以删除HAVING 子句,只需在 ORDER BY 之后添加一个 LIMIT。(因为这将遍历 每一行,我们真的只想将其作为测试。(仅测试几行, 用只拉几行的内联视图替换对表的引用。由于 SQL 是动态生成的,我将其分配给变量$sql = "SELECT ...,并将内容分配给echovardump $sql.
  • @ErrolGreen:另一方面,为了生成 minLon/maxLon,你是“加”还是“减”来得到“min”实际上取决于“Lon”是正数还是正数消极的。或者,做加法和减法得到boundingLon1boundingLon2,然后找出哪个值“更小”。 (你在 SQL 中有 &gt; minLon&lt; maxLon 不等式比较。(你需要确保 minLon &lt; maxLon 或者你不会得到任何行。)HTH
  • 去掉HAVING条件后,我发现我的半径是半径“D”由于某种原因非常高。事实证明,我的 COS 计算中缺少 RADIANS。现在效果很好。非常感谢,你帮了大忙。
【解决方案2】:

我目前正在使用以下工具,并且对(相对)准确性感到满意。

( 
    3958*3.1415926 * sqrt( 
        ( table.latitude - $latitude ) * ( table.latitude - $latitude ) 
        + cos( table.latitude / 57.29578 ) 
        * cos( $latitude / 57.29578 ) 
        * ( table.longitude - $longitude ) 
        * ( table.longitude - $longitude ) 
    ) / 180
) <= $radius 

【讨论】:

  • 感谢您的回复,但它如何计算圆的半径?
  • @Errol Green:这个表达式看起来像一个 MySQL 实现的半正弦公式。 (就个人而言,我会使用 POWER() 函数两次,而不是每次重复这两个表达式两次。)我认为这个公式在数学上等同于您使用的大圆距离公式(“余弦球面定律”) ,不同的是,这个公式在进行浮点计算时舍入误差较小(由于计算机中浮点数的精度有限。)但舍入误差并不大,它只影响小距离。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
  • 2011-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多