我有一个拥有13.000个位置的mysql数据库。使用html地理位置,我通过定义半径(例如1 km)找到用户的位置,我计算一个边界框并使用它来查找该半径内的(sql)位置。输入:地理定位用户,输出:(已排序)半径1公里内的位置数组。
如果该半径内没有位置,则不起作用。我想要的是在附近显示大约10个位置,无论半径如何。这意味着谷歌地图缩放级别应该是动态的,并且sql搜索应该是不同的。输入:地理位置用户,输出:附近有10个位置,没有预定义的半径,地图应该在地图上或多或少可见 - >适当的地图缩放级别。
我正在考虑从1公里开始,如果在该范围内没有位置,则将半径增加+100米并保持循环直到+/- 10个位置(排序并将它们放入数组中) 。然后找到距离用户位置和阵列中最后位置的距离(最大距离),并从那里计算出适当的谷歌地图缩放级别。
我有一个问题:如果最近的位置在20公里范围内怎么办?每个循环增加100米,它将循环计算200次!我担心这会导致等待时间过长。
我该如何解决这个问题?是否有其他方法可以到达附近的地点?
这是我的mysql表结构(其中formid代表一个位置的ID),总共189.031行。
编辑:我已经尝试了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);
答案 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
是位置坐标。
查询在50,068行的数据库上花了0.2506秒