好的 - 我已经和我一起打了大约3个月的摔跤,因为我已经筋疲力尽了我所遇到的每一个地理接近公式而且我没有接近得到正确的结果我认为是时候寻求帮助了。
目标
我正在设置商店定位器的相当基本的实现。用户输入他们的邮政编码并从预定义的搜索半径列表中进行选择。 gmaps API为此地址生成纬度/经度坐标,并将它们传递给php脚本。在此脚本中,将针对mysql数据库表(下面的结构)查询用户坐标
post_id int(11)
post_type varchar(20)
lat float(10,6)
lng float(10,6)
此查询的结果(post ids)被输入到wordpress查询中,该查询生成包含地图标记数据的XML。 (wordpress查询使用post__in和posts_per_page -1来显示查询生成的所有ID的信息
问题
简而言之,我遇到的Haversine公式的每一个实现似乎都会导致缺少标记 - 特别是任何非常接近用户输入坐标的标记(不知道确切但我认为它在500米以内) )。这是一个很大的问题,就好像用户输入他们的邮政编码,并且有一个非常靠近他们位置的商店,它将不会出现。
我已经尝试了论坛的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在这里提出了一个我试过的公式:Geolocation SQL query not finding exact location
此公式似乎显示的标记非常接近用户输入的位置,但错过了应在给定半径内显示的其他标记。要清除任何混淆,这就是我使用的代码:
$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
不适用于那里的每个项目,但根据我的需要,它是完美的(并且有效!)
答案 0 :(得分:2)
修改强> 这个定位器经常出现,我已经在上面写了一篇文章。
http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
原帖
让我们首先处理所有的hasrsine公式,将其放入存储函数中,这样我们就可以忘记它的粗糙细节了。注意:整个解决方案都在法定里程。
DELIMITER $$
CREATE
FUNCTION distance(lat1 FLOAT, long1 FLOAT, lat2 FLOAT, long2 FLOAT)
RETURNS FLOAT
DETERMINISTIC NO SQL
BEGIN
RETURN (3959 * ACOS(COS(RADIANS(lat1))
* COS(RADIANS(lat2))
* COS(RADIANS(long1) - RADIANS(long2))
+ SIN(RADIANS(lat1))
* SIN(RADIANS(lat2))
));
END$$
DELIMITER ;
现在让我们把一个在边界框上搜索的查询放在一起,然后使用我们的距离函数和按距离排序来优化搜索
基于您问题中的PHP代码:
假设$radius
是您的半径,$center_lat
,$center_lng
是您的参考点。
$sqlsquareradius = "
SELECT post_id, lat, lng
FROM
(
SELECT post_id, lat, lng,
distance(lat, lng, " . $center_lat . "," . $center_lng . ") AS distance
FROM wp_geodatastore
WHERE lat >= " . $center_lat . " -(" . $radius . "/69)
AND lat <= " . $center_lat . " +(" . $radius . "/69)
AND lng >= " . $center_lng . " -(" . $radius . "/69)
AND lng <= " . $center_lng . " +(" . $radius . "/69)
)a
WHERE distance <= " . $radius . "
ORDER BY distance
";
请注意一些事情。
首先,它在SQL中而不是在PHP中进行边界框计算。除非在一个环境中保留所有计算,否则没有充分的理由。 (radius / 69)
是radius
法定里程数的度数。
其次,它没有根据纬度来调整纵向边界框的大小。相反,它使用更简单但有点太大的边界框。这个边界框捕获了一些额外的记录,但距离测量摆脱了它们。对于典型的邮政编码/商店查找器应用程序,性能差异可以忽略不计。如果你正在搜索更多的记录(例如所有电线杆的数据库),它可能不会那么微不足道。
第三,它使用嵌套查询进行距离消除,以避免每个项目多次运行距离函数。
第四,按距离ASCENDING命令。这意味着您的零距离结果应首先显示在结果集中。首先列出最近的东西通常是有意义的。
第五,它始终使用FLOAT
而不是DOUBLE
。这是有充分理由的。半径距离公式并不完美,因为它使得近似地球是一个完美的球体。该近似值恰好与FLOAT
数字的epsilon精确度相同。所以DOUBLE
对这个问题具有欺骗性的数字矫枉过正。 (不要使用这个半正式公式来做停车场排水等土木工程工作,否则你会得到几个epsilon,几英寸深的大水坑,我保证。)这对于商店寻找器应用来说很好。
第六,您肯定想要为lat
列创建索引。如果您的地理位置信息不会经常更改,则有助于为您的lng
列创建索引。但是,您的lat
索引会为您提供大部分查询效果。
最后,我测试了存储过程和SQL,但没有测试PHP。
参考:http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL 此外,我还有一系列医疗保健设施的近距离查找器。
---------------编辑--------------------
如果您没有可以定义存储过程的用户界面,那就太麻烦了。无论如何,PHP允许您在sprintf调用中使用编号参数,因此您可以像这样生成整个嵌套语句。注意:您可能需要%$ 1f等。您需要对此进行试验。
$sql_stmt = sprintf ("
SELECT post_id, lat, lng
FROM
(
SELECT post_id, lat, lng,
(3959 * ACOS(COS(RADIANS(lat))
* COS(RADIANS(%$1s))
* COS(RADIANS(lng) - RADIANS(%$2s))
+ SIN(RADIANS(lat))
* SIN(RADIANS(%$1s))
))
AS distance
FROM wp_geodatastore
WHERE lat >= %$1s -(%$3s/69)
AND lat <= %$1s +(%$3s/69)
AND lng >= %$2s -(%$3s/69)
AND lng <= %$2s +(%$3s/69)
)a
WHERE distance <= %$3s
ORDER BY distance
",$center_lat,$center_lng, $radius);
答案 1 :(得分:0)
这是我在自己的地理邻近度计算中成功使用了一段时间的解决方案:
/**
* This portion of the routine calculates the minimum and maximum lat and
* long within a given range. This portion of the code was written
* by Jeff Bearer (http:return true;//www.jeffbearer.com).
*/
$lat = somevalue; // The latitude of our search origin
$lon = someothervalue; // The longitude of our search origin
$range = 50; // The range of our search, in miles, of your zip
// Find Max - Min Lat / Long for Radius and zero point and query only zips in that range.
$lat_range = $range / 69.172;
$lon_range = abs($range / (cos($lon) * 69.172));
$min_lat = number_format($lat - $lat_range, '4', '.', '');
$max_lat = number_format($lat + $lat_range, '4', '.', '');
$min_lon = number_format($lon - $lon_range, '4', '.', '');
$max_lon = number_format($lon + $lon_range, '4', '.', '');
/* Query for matching zips:
SELECT post_id, lat, lng
FROM wp_geodatastore
WHERE
lat BETWEEN $min_lat AND $max_lat
AND lng BETWEEN $min_lon AND $max_lon
*/
答案 2 :(得分:0)
您可以在http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html尝试我的课程。它使用harvesine公式和hilbert曲线计算四核。然后,您可以从左到右搜索四核。键的每个位置都是怪物曲线上的一个点。可以在Nick的空间索引quadtree hilbert曲线博客中找到对曲线的更好解释。这就像使用mysql的空间索引扩展,但你有更多的控制权。您可以使用z曲线或摩尔曲线,也可以更改外观。
答案 3 :(得分:0)
这是来自正在运行的生产系统的代码,
6371.04 * acos(cos(pi()/2-radians(90-wgs84_lat)) * cos(pi()/2-radians(90-$lat)) * cos(radians(wgs84_long)-radians($lon)) + sin(pi()/2-radians(90-wgs84_lat)) * sin(pi()/2-radians(90-$lat))) as distance
使用不同的距离公式,但对于商店定位器,差异很小。
答案 4 :(得分:0)
横向思考我已经想出了一种解决缺失标记问题的“解决方案”。我最初发布的两个方程给出了正确的结果,但每个方程都错过了靠近目标或搜索半径边缘的标记
它不是很优雅,但我认为运行两个方程并生成2个数组然后我合并(删除任何重复项)将给我所有我正在寻找的标记。这确实有效(显然是性能受到影响,但它不是一个高流量的应用程序)所以我暂时会使用它,但如果有人有一个更实用的解决方案,我仍然可以使用它!