EDIT这个定位器出现的频率足够高,我已经写了一篇关于它的文章。
http://www.plumislandmedia.net/mysql/havesine-mysql-nearest-loc/ http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
原帖
让我们首先处理一下半正弦公式,将其放入一个存储函数中,这样我们就可以忘记它的粗糙细节。注意:整个解决方案以法定英里为单位。
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 代码:
Assume $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
法定里程。
其次,它不会根据纬度调整纵向边界框的大小。相反,它使用一个更简单但稍微太大的边界框。这个边界框捕获了一些额外的记录,但距离测量消除了它们。对于典型的邮政编码/商店查找应用程序,性能差异可以忽略不计。如果您搜索更多记录(例如所有电线杆的数据库),它可能不会那么微不足道。
第三,它使用嵌套查询来消除距离,以避免为每个项目多次运行距离函数。
第四,它按距离升序排序。这意味着您的零距离结果应该首先显示在结果集中。首先列出最近的事物通常是有意义的。
五、它使用FLOAT
而不是DOUBLE
自始至终。这是有充分理由的。半正矢距离公式并不完美,因为它近似地认为地球是一个完美的球体。该近似值恰好与 epsilon 的准确度大致相同。FLOAT
数字。所以DOUBLE
对于这个问题来说,数字是具有欺骗性的杀伤力。 (不要使用这个半正矢公式来做停车场排水等土木工程工作,否则你会得到几个 epsilon 的大水坑,几英寸深,我保证。)这对于商店查找应用程序来说很好。
第六,您肯定会想要为您的内容创建一个索引lat
柱子。如果您的位置表不经常更改,那么为您的位置创建索引将有所帮助lng
专栏也是如此。但你的lat
索引将为您带来大部分查询性能提升。
最后,我测试了存储过程和 SQL,但没有测试 PHP。
参考:http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL 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);