« 2011年1月的文章归档

基于地图坐标的范围查找

基于LBS的应用中,可能有如下命题:给定一个坐标点(如:lat=32.0330891313519 ,lng=118.736731410027),查找出给定距离(如1公里)内的其它坐标(人,商家或别的有坐标信息的事物)。

一、直接在mysql中过滤有如下方法:

DELIMITER //
CREATE FUNCTION geo_distance(lat1 FLOAT, lng1 FLOAT, lat2 FLOAT, lng2 FLOAT) RETURNS FLOAT
BEGIN

	DECLARE r INT DEFAULT 6378137;
	DECLARE s0 FLOAT;
	DECLARE s1 FLOAT;
	DECLARE e0 FLOAT;
	DECLARE e1 FLOAT;

	DECLARE d0 FLOAT;
	DECLARE d1 FLOAT;

	SET s0 = RADIANS(lat1);
	SET s1 = RADIANS(lng1);
	SET e0 = RADIANS(lat2);
	SET e1 = RADIANS(lng2);

	SET d0 = ABS(s0 - e0);
	SET d1 = ABS(s1 - e1);

	RETURN r * 2 * ASIN(SQRT(POW(SIN(d0/2),2) + COS(s0)*COS(e0)*POW(SIN(d1/2),2)));

END//

DELIMITER ;

然后使用过虑条件:

SELECT * FROM table WHERE geo_distance(lat , lng, 32.0330891313519, 118.736731410027) <1000;

小表里可以使用,大表里结局是悲惨的,速度慢,效率差。

二、在PHP中过滤

我们可以把该表全部查出来,再循环对比每一条记录。

/**
 * Calculate the distance between two coordinates.
 * @param array $s, array(float:latitude, float: longitude)
 * @param array $e, array(float:latitude, float: longitude)
 * @return float, the distance
 */
function geo_distance($s, $e) {

	//earth's mean radius in KM
	$r = 6378.137;

	$s[0] = deg2rad($s[0]);
	$s[1] = deg2rad($s[1]);

	$e[0] = deg2rad($e[0]);
	$e[1] = deg2rad($e[1]);

	$d0 = abs($s[0] - $e[0]);
	$d1 = abs($s[1] - $e[1]);

	$p = pow(sin($d0/2), 2) + cos($s[0]) * cos($e[0]) * pow(sin($d1/2), 2);

	$ds = $r * 2 * asin(sqrt($p));

	return $ds;
}
while($row = $db->fetch_array($query)) {
    if(geo_distance(array($row['lat'] , $row['lng']), array(32.0330891313519, 118.736731410027))<1000){
        //这里得出的就是小于1公里的点
    }
}

通过实践,结局依然是悲惨的。速度慢,效率差。

三、使用sphinx

1)索引配置文件:

sql_query                      = SELECT RADIANS(lat) AS lat , RADIANS(lng) AS lng FROM table
sql_attr_float                  = lat
sql_attr_float                  = lng

2)生成索引

3)code:

 include_once("./source/sphinxapi.php");
 $sphinx = new SphinxClient();
 $sphinx->SetServer('127.0.0.1' , 9312);
 $sphinx->SetGeoAnchor('lat' , 'lng' , deg2rad($lat) , deg2rad($lng));
 $sphinx->SetFilterFloatRange ( '@geodist', 0.0 , 1000.0, false );
 $sphinx->SetLimits ( 0 , 1000);
 $return = $sphinx->Query('' , "*");

这里有几个需要注意的地方我们从MAP API(如google map , map abc)里得到的坐标是这样子的:32.0330891313519, 118.736731410027。

sphinx中API函数SetGeoAnchor()四个参数都是以弧度为单位,在mysql中角度转弧度我们使用RADIANS函数,在PHP中我们使用deg2rad函数,接着,我们可以使用API过滤@geodist(单位:米)距离在1公里内数据。

此法是我在如厕时想出来的,果断使用,效率高,速度快!