附近具有适当缩放级别的位置

时间:2014-05-16 21:00:09

标签: mysql google-maps geolocation

我有一个拥有13.000个位置的mysql数据库。使用html地理位置,我通过定义半径(例如1 km)找到用户的位置,我计算一个边界框并使用它来查找该半径内的(sql)位置。输入:地理定位用户,输出:(已排序)半径1公里内的位置数组。

如果该半径内没有位置,则不起作用。我想要的是在附近显示大约10个位置,无论半径如何。这意味着谷歌地图缩放级别应该是动态的,并且sql搜索应该是不同的。输入:地理位置用户,输出:附近有10个位置,没有预定义的半径,地图应该在地图上或多或少可见 - >适当的地图缩放级别。

我正在考虑从1公里开始,如果在该范围内没有位置,则将半径增加+100米并保持循环直到+/- 10个位置(排序并将它们放入数组中) 。然后找到距离用户位置和阵列中最后位置的距离(最大距离),并从那里计算出适当的谷歌地图缩放级别。

我有一个问题:如果最近的位置在20公里范围内怎么办?每个循环增加100米,它将循环计算200次!我担心这会导致等待时间过长。

我该如何解决这个问题?是否有其他方法可以到达附近的地点?

这是我的mysql表结构(其中formid代表一个位置的ID),总共189.031行。

enter image description here

编辑:我已经尝试了Ollie jones的答案,这是我使用的脚本(我将计数器除以2,因为我有每个地址的语言副本):

 $rad = 0.2;  // radius of bounding circle in kilometers

 $R = 6371;  // earths mean radius, km

  // first-cut bounding box (in degrees)
  $maxLat = $_GET['lat'] + rad2deg($rad/$R);
  $minLat = $_GET['lat'] - rad2deg($rad/$R);
  // compensate for degrees longitude getting smaller with increasing latitude
  $maxLon = $_GET['long'] + rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));
  $minLon = $_GET['long'] - rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));

$start = microtime(true);
for ($i = 0; ; $i++) {

  // first-cut bounding box (in degrees)
  $maxLat = $_GET['lat'] + rad2deg($rad/$R);
  $minLat = $_GET['lat'] - rad2deg($rad/$R);
  // compensate for degrees longitude getting smaller with increasing latitude
  $maxLon = $_GET['long'] + rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));
  $minLon = $_GET['long'] - rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));

$sql2 = "SELECT * FROM table WHERE content BETWEEN '".$minLat."' AND '".$maxLat."'";

$result3 = mysqli_query($link, $sql2);  

$counter = 0;
while ($row3 = mysqli_fetch_assoc($result3)) {

$sql3 = "SELECT * FROM table WHERE attribute = 'LON' AND formid = {$row3["formid"]} AND content BETWEEN '".$minLon."' AND '".$maxLon."'";

$result4 = mysqli_query($link, $sql3);  

while ($row4 = mysqli_fetch_assoc($result4)) {
    $counter = $counter + 1;; // or $counter = $counter + 1;
}
}
$total = $counter/2;
echo $total;    
if ($total >= 2 || $rad >= 30) {
$end = microtime(true);
$time = number_format(($end - $start), 2);
echo 'This page loaded in ', $time, ' seconds';
break;  

}
$rad = $rad * sqrt(2);
}
/* close connection */
mysqli_close($link);

3 个答案:

答案 0 :(得分:2)

注意在OQ发布了一些代码后,我发布了另一个问题的答案。请看看。这个问题比这个答案更重要。

您的基本想法似乎没问题:也就是说,如果您在第一次尝试中没有获得足够的分数,请增加搜索范围。

您建议每次尝试时将搜索半径增加100米。这似乎是一种非侵略性的搜索范围扩展策略。

相反,为什么不每次将当前半径的半径提高41.4%(半径* sqrt(2))?这样,您将每次迭代搜索的地理区域加倍。你的查询已经返回最接近的十个点,所以即使你在一次迭代中突然获得一千点,你也不会得到疯狂的结果。

请注意,如果您的13,000点是邮政编码/邮政编码质心,那么1公里不是此搜索的良好起点。除了在密集的城市地区,你不可能在任何特定的1公里范围内找到10个。你可能想要开始做大。

修改感谢您更新问题,以包含有关表格结构和查询的信息。这有很大帮助。

您提出了一个困难的优化问题。您的纬度和经度值存储在属性表中。据推测,它们存储为文本“-45.12345”而不是FLOAT值。优化这些地理查询要求至少使用纬度值上的顺序可扫描索引。也就是说,您需要能够在SQL中说出类似的内容。

SELECT whatever FROM sometable 
 WHERE attribute = 'LAT'
   AND content BETWEEN ?lat-radius AND ?lat+radius

?lat是候选点的纬度。)服务器需要能够通过随机访问从?-radius开始的索引然后按顺序扫描到{{1}来满足该请求}。服务器无法做到这一点:您的查询包含从文本到FLOAT的隐式类型转换。类型转换失败了索引。

?+radius

因此,除非您更改架构,因此LAT和LONG可以是带索引的FLOAT值,此查询将变得缓慢,与搜索的SELECT whatever FROM sometable WHERE attribute = 'LAT' AND CAST(content AS FLOAT) BETWEEN ?lat-radius AND ?lat+radius 无关。小radius无济于事,大radius也不会受到伤害。

SO:提高性能的最简单方法是执行一次查询。我首先建议的半径的迭代扩展对于你的表结构是没有意义的。使用较大的半径(50km),并使用radius将最近的点移至候选点。

这个问题还有另一类解决方案。它涉及使用某种触发器或其他更新方法创建shadow lat / long表。但这是很多工作,你可能不会这样做。

仅供参考,这是关于解决地理位置问题的一篇文章。 http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

答案 1 :(得分:0)

解决方案是在20公里半径范围内没有显示位置。另一种解决方案是计算voronoi图。然后,您可以使用该图来过滤附近的城市。你可以在这里阅读一个例子:http://alastaira.wordpress.com/2011/04/25/nearest-neighbours-voronoi-diagrams-and-finding-your-nearest-sql-server-usergroup/

答案 2 :(得分:0)

注意:在将架构和代码添加到问题之前给出了此答案。我将离开它,因为它可能在将来帮助其他人。

以下SQL查询使用Spherical Law of Cosines来计算表中坐标和坐标之间的距离。它将结果限制为10并按距离排序。

  

d = acos(sin(lat1).sin(lat2)+ cos(lat1).cos(lat2).cos(lng2-lng1))。R

SELECT  name, lat, lng, ( 3959 * acos( cos( radians($center_lat) ) 
                        * cos( radians( lat ) ) * cos( radians( lng )
                        - radians($center_lng) ) + sin( radians($center_lat) ) 
                        * sin( radians( lat ) ) ) ) AS distance FROM table 
                        ORDER BY distance LIMIT 0 , 10

$center_lat& $center_lng是位置坐标。

查询使用SQL Math functions

查询在50,068行的数据库上花了0.2506秒