PHP MySQL获取用户位置的GPS半径内位置。

5
我在我的数据库中有汽车事故的记录。这些事件都有纬度和经度。在使用GPS的移动设备上,我可以获取用户的位置及其坐标。用户可以选择一个半径,以了解周围是否有事故发生。假设他想知道距离他2英里范围内的事故。
因此,我从手机向Web服务发送用户的纬度、经度和他选择的半径。我需要进行SQL查询以获取距离用户2英里范围内的事故。
您有任何想法如何实现吗?
5个回答

3
计算距离是非常消耗计算资源的,正如其他人所说。返回大量数据集也不是一个很好的主意 - 特别是考虑到PHP在性能方面并不是很出色。
我会使用一种启发式方法,例如用简单的加减法来近似距离。
1分钟=1.86公里=1.15英里
只需在该范围内搜索数据库中的事件(实际上是一个正方形,而不是一个圆),然后您可以使用PHP处理这些事件。
编辑:这里有一种替代方法;一种计算成本远低于传统方法的近似方法:
以英里为单位近似距离:
sqrt(x * x + y * y)

where x = 69.1 * (lat2 - lat1) 
and y = 53.0 * (lon2 - lon1) 

您可以通过添加余弦数学函数来提高此近似距离计算的准确性:
改进后的近似英里数:
sqrt(x * x + y * y)

where x = 69.1 * (lat2 - lat1) 
and y = 69.1 * (lon2 - lon1) * cos(lat1/57.3) 

来源:http://www.meridianworlddata.com/Distance-Calculation.asp


编辑2:我使用随机生成的数据集进行了一系列测试。

  • 3种算法的准确度差异微小,特别是在短距离情况下
  • 最慢的算法(具有大量三角函数的算法)比其他两个算法慢4倍。

绝对不值得。只需使用近似值即可。

代码在此处:http://pastebin.org/424186


另一个好的启发式方法是使用最小可能的距离,即一分钟(或相关的子分)可以到达的距离,并使用它来排除任何明显超过两英里的候选点。当然,您可以更加聪明地利用用户位置基于各种近似值。 - timdev

2
function distance($lat1,$lon1,$lat2,$lon2,$unit)
  {
  $theta=$lon1-$lon2;
  $dist=sin(deg2rad($lat1))*sin(deg2rad($lat2))+cos(deg2rad($lat1))*cos(deg2rad($lat2))*cos(deg2rad($theta));
  $dist=acos($dist);
  $dist=rad2deg($dist);
  $miles=$dist*60*1.1515;
  $unit=strtoupper($unit);
  if ($unit=="K")
    {
    return ($miles*1.609344);
    }
    else if ($unit=="N")
    {
    return ($miles*0.8684);
    }
    else
    {
    return $miles;
    }
  } // end function

$x_lat=center_of_serach;
$x_lon=center_of_serach;
$_distance=some_distance_in_miles;
$query1 = "SELECT * FROM `location_table` WHERE somefield=somefilter";
$result=mysql_db_query($db_conn, $query1);
$max_rows=mysql_num_rows($result); 
if ($max_rows>0)
  {
while ( $data1=mysql_fetch_assoc($result) )
  {
  if ( distance($x_lat,$x_lon,$data1['lat'],$data1['lng'],'m')<$_distance )
    {
    //do stuff
    }
  }

如果你的数据库不太大,通过函数获取所有数据并运行要比使用查询更快。

这对于公斤和海里也适用。 ;)


哈哈,我刚刚注意到你发的链接了...没错,那个有效。;) - Talvi Watia

1
 SELECT 3963 * ACOS(
    SIN(RADIANS($pointAlat)) * SIN(RADIANS($pointAlat)) + COS(RADIANS($pointAlat))  * COS(RADIANS($pointBlat)) * COS(RADIANS($pointAlong) - RADIANS($pointBlong)))
 AS
 distance;

另外,如果你想找一些关于这个主题的好文章/教程...可以看看这里 http://www.phpfreaks.com/forums/index.php/topic,208965.0.html


由于无法在WHERE子句中使用别名,因此这并不实用 - 这将强制他两次计算一堆超越函数。哎呀。在可能的大型数据集上。双倍哎呀。 - NullUserException

0

有一个公式可以计算两个纬度/经度坐标之间的距离。但要注意,这个公式计算量相当大,所以如果你有很多事件,你需要聪明地处理。首先,阅读涉及的数学知识

至于PHP代码,快速搜索得到了这个链接,看起来应该是有效的。

现在,您可能想使用一些更有效的方法将您的事件点分成两组:那些可能在范围内的点(希望是一个较小的集合),以及那些您可以完全排除的点。检查超过几十个事件坐标可能会影响性能。

我没有任何特别的见解,但如果没有其他人提出聪明的想法,我会尽力自己想出一些方法,如果时间允许的话。


我也会推荐这样做。我会考虑对数据集进行两次处理。第一次处理只是检查事件经度是否在位置经度的X英里范围内,以及事件纬度是否在位置纬度的X英里范围内。这是一个非常简单的查询,将返回一个较小的数据集供处理。有了这个较小的数据集,开始进行一些三角函数计算,以确定事件与用户位置之间的线性距离,这需要更复杂的数学计算,但现在你至少可以使用一个较小的数据集来处理。 - Jake Wilson
@Jakobud,您可以在同一个查询中运行该筛选器:WHERE lat<='".$_distance."' AND lng<='".$_distance."'"; - Talvi Watia
是的,就像那样。为了可视化Jackobud的方法,您首先要检查任何候选点是否在以用户坐标为中心的4英里正方形(16平方英里)区域内。如果是,则进行更昂贵的计算以确定它是否在该正方形内刻画的圆圈内[http://en.wikipedia.org/wiki/Inscribed_circle]。 - timdev
@Talvi Watia - 嗯?那似乎不对啊--单位不匹配。 - timdev
@timedev 哇,是啊,我发帖太快了。加上 $distance/69.1 大约可以转换为度数。 - Talvi Watia

0

我进行了快速搜索,找到了这篇博客文章,其中提供了一个很好的解释和SQL语句来选择给定半径内的记录。

在评论中,他建议“对于大型数据集的速度,您可能需要先抓取原点周围的正方形块,方法是将原点的纬度/经度各加上一英里左右,然后使用上述内容作为子查询从中间开始处理”,这听起来像是正确的方法。


链接页面已存档在web.archive.org - showdev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接