由于地理位置邻近公式(商店定位器)而导致结果缺失
好的 - 我已经断断续续地与这个问题斗争了大约 3 个月,因为我已经用尽了我遇到的所有地理邻近公式,但我距离获得正确的结果还差得很远,我想是时候了寻求帮助。
目标
我正在建立一个相当基本的商店定位器实现。用户输入他们的邮政编码并从预定义的搜索半径列表中进行选择。 gmaps API 生成该地址的纬度/经度坐标并将其传递给 php 脚本。在此脚本中,根据 mysql 数据库表(结构如下)查询用户坐标。
post_id int(11)
post_type varchar(20)
lat float(10,6)
lng float(10,6)
该查询的结果(帖子 ID)被输入到 wordpress 查询中,该查询生成包含地图标记数据的 XML。 (wordpress 查询使用 post__in 和 posts_per_page -1 显示查询生成的所有 ID 的信息
问题
简而言之,我遇到的半正矢公式的每个实现似乎都会导致丢失标记- 特别是任何距离用户输入的坐标非常接近的标记(不知道确切的位置,但我认为它在大约 500m 之内),这是一个大问题,就好像用户输入他们的邮政编码并且附近有一家商店一样。 。
我已经尝试了从各种教程中挖掘出的大约 8 种不同的论坛排列,结果相同,下面是我目前在网站上使用的公式,它提供了所有结果 除了那些非常接近用户输入位置的标记之外:
$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];
// Calculate square radius search
$lat1 = (float) $center_lat - ( (int) $radius / 69 );
$lat2 = (float) $center_lat + ( (int) $radius / 69 );
$lng1 = (float) $center_lng - (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );
$lng2 = (float) $center_lng + (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );
$sqlsquareradius = "
SELECT
post_id, lat, lng
FROM
wp_geodatastore
WHERE
lat BETWEEN ".$lat1." AND ".$lat2."
AND
lng BETWEEN ".$lng1." AND ".$lng2."
"; // End $sqlsquareradius
// Create sql for circle radius check
$sqlcircleradius = "
SELECT
t.post_id,
3956 * 2 * ASIN(
SQRT(
POWER(
SIN(
( ".(float) $center_lat." - abs(t.lat) ) * pi() / 180 / 2
), 2
) + COS(
".(float) $center_lat." * pi() / 180
) * COS(
abs(t.lat) * pi() / 180
) * POWER(
SIN(
( ".(float) $center_lng." - t.lng ) * pi() / 180 / 2
), 2
)
)
) AS distance
FROM
(".$sqlsquareradius.") AS t
HAVING
distance <= ".(int) $radius."
ORDER BY distance
"; // End $sqlcircleradius
$result = mysql_query($sqlcircleradius);
$row = mysql_fetch_array( $result );
while($row = mysql_fetch_array( $result )) {
// the contents of each row
$post_ids[] = $row['post_id'];
}
我尝试过 Mike Pelley 在这里建议的 1 个公式:地理位置 SQL 查询未找到确切位置
这个公式似乎显示了非常重要的标记靠近用户输入的位置,但错过了应该在给定半径内显示的其他位置。为了消除任何混乱,这是我使用的代码:
$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];
$sql = "
SELECT post_id, lat, lng,
truncate((degrees(acos( sin(radians(lat))
* sin(radians(".$center_lat."))
+ cos(radians(lat))
* cos(radians(".$center_lat."))
* cos(radians(".$center_lng." - lng) ) ) )
* 69.09*1.6),1) as distance
FROM wp_geodatastore HAVING distance <= ".$radius." ORDER BY distance desc
"; // End $sqlcircleradius
$result = mysql_query($sql);
$row = mysql_fetch_array( $result );
while($row = mysql_fetch_array( $result )) {
// Print out the contents of each row
$post_ids[] = $row['post_id'];
}
请求
基本上我想知道为什么这些代码块都没有显示正确的标记。如果有人可以对代码提出任何改进建议,或者可以向我指出一些我可能错过的资源,那就太好了
编辑
我认为我的 psudeo 答案有效,但事实证明发现仍然有问题。我现在最终采用了一种非常不同的策略,我正在使用一个非常好的jquery存储定位器,可以在这里找到:http://www.bjornblog.com/web/jquery-store-locator-plugin
并不适用于每个项目,但对于我的需要来说它是完美的(并且有效!)
OK - I've been wrestling with this for about 3 months on and off and since I've exhausted every geo proximity formula out there that I've come across and I'm no closer to getting the right results I figured it time to ask for some help.
THE AIM
I'm setting up a fairly basic implementation of a store locator. The user enters their postcode and selects from a predefined list of search radii. The gmaps API generates lat/long coordinates for this address and passes them to a php script. In this script the user coords are queried against a mysql database table (structure below)
post_id int(11)
post_type varchar(20)
lat float(10,6)
lng float(10,6)
The results of this query (post ids) are entered into a wordpress query which generates the XML that contains the map marker data. (the wordpress query uses post__in and posts_per_page -1 to display info for all ID generated by the query
THE PROBLEM
In a nutshell, every implementation of the Haversine formula I've come across seems to result in missing markers - specifically any markers that are very close to the users entered coordinates (don't know precisely but I think it's within about 500m). This is a big problem as if the user enters their postcode and there is a store very close to their location it won't show up.
I've tried about 8 different permutations of the forumla that I've dug up from various tutorials with the same results. Below is the formula that I'm currently using on the site which provides all markers except for the those very close to the users entered position:
$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];
// Calculate square radius search
$lat1 = (float) $center_lat - ( (int) $radius / 69 );
$lat2 = (float) $center_lat + ( (int) $radius / 69 );
$lng1 = (float) $center_lng - (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );
$lng2 = (float) $center_lng + (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );
$sqlsquareradius = "
SELECT
post_id, lat, lng
FROM
wp_geodatastore
WHERE
lat BETWEEN ".$lat1." AND ".$lat2."
AND
lng BETWEEN ".$lng1." AND ".$lng2."
"; // End $sqlsquareradius
// Create sql for circle radius check
$sqlcircleradius = "
SELECT
t.post_id,
3956 * 2 * ASIN(
SQRT(
POWER(
SIN(
( ".(float) $center_lat." - abs(t.lat) ) * pi() / 180 / 2
), 2
) + COS(
".(float) $center_lat." * pi() / 180
) * COS(
abs(t.lat) * pi() / 180
) * POWER(
SIN(
( ".(float) $center_lng." - t.lng ) * pi() / 180 / 2
), 2
)
)
) AS distance
FROM
(".$sqlsquareradius.") AS t
HAVING
distance <= ".(int) $radius."
ORDER BY distance
"; // End $sqlcircleradius
$result = mysql_query($sqlcircleradius);
$row = mysql_fetch_array( $result );
while($row = mysql_fetch_array( $result )) {
// the contents of each row
$post_ids[] = $row['post_id'];
}
There was 1 formula that I tried that was suggested by Mike Pelley here: Geolocation SQL query not finding exact location
This formula seemed to show markers that were very close to the users entered location but missed out others that should have been displayed within the given radius. To clear up any confusion this is the code I used:
$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];
$sql = "
SELECT post_id, lat, lng,
truncate((degrees(acos( sin(radians(lat))
* sin(radians(".$center_lat."))
+ cos(radians(lat))
* cos(radians(".$center_lat."))
* cos(radians(".$center_lng." - lng) ) ) )
* 69.09*1.6),1) as distance
FROM wp_geodatastore HAVING distance <= ".$radius." ORDER BY distance desc
"; // End $sqlcircleradius
$result = mysql_query($sql);
$row = mysql_fetch_array( $result );
while($row = mysql_fetch_array( $result )) {
// Print out the contents of each row
$post_ids[] = $row['post_id'];
}
THE REQUEST
Basically I would like to know why neither of these blocks of code are displaying the correct markers. If anyone can suggest any improvements to the code or can point me towards some resource that I might have missed that would be great
EDIT
Thought my psudeo answer was working but as it turns out that was still having problems. I've ended up going for a very different tack now and I'm using a very good jquery store locator which can be found here: http://www.bjornblog.com/web/jquery-store-locator-plugin
Won't work for every project out there but for my needs it's perfect (and works!)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
编辑
这个定位器出现的频率足够高,我已经写了一篇关于它的文章。
http://www.plumislandmedia.net/mysql/haversine-mysql-nearest- loc/
原始帖子
让我们首先一次性处理半正弦公式,将其放入存储函数中,这样我们就可以忘记它的粗糙细节。注意:整个解决方案以法定英里为单位。
现在,让我们组合一个在边界框上搜索的查询,然后使用距离函数和按距离排序来细化搜索。
基于您问题中的 PHP 代码:
假设
$radius
是您的半径,$center_lat
、$center_lng
是您的参考点。请注意与此相关的一些事情。
首先,它使用 SQL 而不是 PHP 进行边界框计算。除了将所有计算保留在一个环境中之外,没有充分的理由这样做。
(radius / 69)
是以radius
法定英里为单位的度数。其次,它不会根据纬度调整纵向边界框的大小。相反,它使用一个更简单但稍微太大的边界框。这个边界框捕获了一些额外的记录,但距离测量消除了它们。对于典型的邮政编码/商店查找应用程序,性能差异可以忽略不计。如果您要搜索更多记录(例如所有电线杆的数据库),它可能不会那么微不足道。
第三,它使用嵌套查询来消除距离,以避免为每个项目多次运行距离函数。
第四,它按距离升序排序。这意味着您的零距离结果应该首先显示在结果集中。首先列出最近的事物通常是有意义的。
第五,它始终使用
FLOAT
而不是DOUBLE
。这是有充分理由的。半正矢距离公式并不完美,因为它近似地认为地球是一个完美的球体。这种近似值的精确度恰好与FLOAT
数字的 epsilon 大致相同。因此,对于这个问题,DOUBLE
是一种具有欺骗性的数字杀伤力。 (不要使用这个半正矢公式来做停车场排水等土木工程工作,否则你会得到几个 epsilon 的大水坑,几英寸深,我保证。)这对于商店查找应用程序来说很好。第六,您肯定希望为
lat
列创建索引。如果您的位置表不经常更改,那么为lng
列创建索引也会有所帮助。但是您的lat
索引将为您带来大部分查询性能提升。最后,我测试了存储过程和 SQL,但没有测试 PHP。
参考:http://www.scribd.com/doc/2569355 /地理距离搜索-with-MySQL
还有我在医疗保健机构使用大量邻近探测器的经验。
--------------- 编辑 --------------------
如果您没有可让您定义的用户界面存储过程,这很麻烦。无论如何,PHP 允许您在 sprintf 调用中使用编号参数,因此您可以像这样生成整个嵌套语句。注意:您可能需要 %$1f 等。您需要对此进行试验。
EDIT
This location-finder comes up often enough that I've written an article on it.
http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
Original Post
Let's start by dealing with the haversine formula once for all, by putting it into a stored function so we can forget about its gnarly details. NOTE: This whole solution is in statute miles.
Now let's put together a query that searches on the bounding box, and then refines the search with our distance function and orders by distance
Based on the PHP code in the your question:
Assume
$radius
is your radius,$center_lat
,$center_lng
is your reference point.Notice a few things about this.
First, it does the bounding box computation in SQL rather than in PHP. There's no good reason for that, except keeping all the computation in one environment.
(radius / 69)
is the number of degrees inradius
statute miles.Second, it doesn't fiddle with the size of the longitudinal bounding box based on latitude. Instead it uses a simpler, but slightly too large, bounding box. This bounding box catches a few extra records, but the distance measurement gets rid of them. For your typical postcode / store finder app the performance difference is negligible. If you were searching many more records (e.g. a database of all utility poles) it might not be so trivial.
Third, it uses a nested query to do the distance elimination, to avoid having to run the distance function more than once for each item.
Fourth, it orders by distance ASCENDING. This means your zero-distance results should show up first in the result set. It usually makes sense to list nearest things first.
Fifth, it uses
FLOAT
rather thanDOUBLE
throughout. There's a good reason for that. The haversine distance formula is not perfect, because it makes the approximation that the earth is a perfect sphere. That approximation happens to break down at roughly the same level of accuracy as the epsilon forFLOAT
numbers. SoDOUBLE
is deceptive numerical overkill for this problem. (Don't use this haversine formula to do civil engineering work like parking lot drainage, or you will get big puddles a couple of epsilon, a few inches, deep, I promise.) It's fine for store-finder applications.Sixth, you are definitely going to want to create an index for your
lat
column. If your table of locations doesn't change very often, it will help to create an index for yourlng
column as well. But yourlat
index will give you most of your query performance gain.Lastly, I tested the stored procedure and the SQL, but not the PHP.
Reference: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Also my experience with a bunch of proximity finders for health care facilities.
--------------- EDIT --------------------
If you don't have a user interface that lets you define a stored procedure, that's a nuisance. At any rate, PHP lets you use numbered parameters in the sprintf call, so you can generate the whole nested statement like this. NOTE: You might need %$1f etc. You'll need to experiment with this.
这是我在自己的地理邻近度计算中成功使用了一段时间的解决方案:
Here's a solution I used successfully for a while in my own geo proximity calculations:
这是来自工作生产系统的代码,
使用不同的距离公式,但对于商店定位器来说差异很小。
This is code from a working production system,
Uses a different distance formular, but for a store locator the difference is minimal.
稍微横向思考一下,我想出了一种解决标记缺失问题的“某种”解决方案。我最初发布的两个方程给出了正确的结果,但每个方程都错过了靠近目标或搜索半径边缘的标记
这不是很优雅,但我认为运行这两个方程并生成 2 个数组,然后将它们组合起来(删除任何重复项)会给我我正在寻找的所有标记。这确实有效(显然会影响性能,但它不是高流量应用程序),所以我暂时会使用它,但如果有人有一个更实用的解决方案,我仍然在寻求一个更实用的解决方案!
Thinking a little laterally I've come up with a 'sort of' solution to the problem of the missing markers. The two equations I posted originally gave the correct results but each missed out either markers close to the target or on the edges of the search radius
It's not very elegant but I figured that running both equations and producing 2 arrays which I then combined (removing any duplicates) would give me all the markers I'm looking for. This does work (obviously a performance hit but it's not a high traffic application) so I'll work with this for the time being but I'm still after a more practical solution if anyone has one!
您可以在 http:// /www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html。它使用 Harvesine 公式和希尔伯特曲线来计算四密钥。然后您可以从左到右搜索四键。关键点的每个位置都是怪物曲线上的一个点。该曲线的更好解释可以在 Nick 的空间索引四叉树希尔伯特曲线博客中找到。这就像使用 mysql 的空间索引扩展,但你有更多的控制权。您可以使用 az 曲线或 moore 曲线,也可以更改外观。
You can try my class at http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html. It uses the harvesine formula and a hilbert curve to compute a quadkey. You can then search the quadkey from left to right. Every position of the key is a point on the monster curve. A better explanation of the curve can be found at Nick's spatial index quadtree hilbert curve blog. It's like using the spatial index extension from mysql but you have more control. You can use a z curve or moore curve or you can change the look.