题图来自网络
工作中某些业务需要用到按距离排序返回结果,之前的方式是根据前端传过来来的经纬度,和指定范围的距离,算出一个坐标区间,再用这个区间的值去MySQL中查找,类似“where lat between (lat1, lat2) and lng between (lng1,lng2)”,查出数据后,再遍历数据计算每一条数据到这个经纬度的距离,然后根据得出的距离排序返回。低效,麻烦,不方便分页。
于是决定直接从MySQL中算出距离后返回,省事,方便,还可以直接分页了。
查资料后发现还挺简单的,下方的示例是从Google官方的文档中摘取出来。
创建如下数据表:
CREATE TABLE `markers` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 60 ) NOT NULL ,
`address` VARCHAR( 80 ) NOT NULL ,
`lat` FLOAT( 10, 6 ) NOT NULL ,
`lng` FLOAT( 10, 6 ) NOT NULL
) ENGINE = MYISAM ;
填充数据:
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Frankie Johnnie & Luigo Too','939 W El Camino Real, Mountain View, CA','37.386339','-122.085823');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Amici\'s East Coast Pizzeria','790 Castro St, Mountain View, CA','37.38714','-122.083235');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Kapp\'s Pizza Bar & Grill','191 Castro St, Mountain View, CA','37.393885','-122.078916');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Round Table Pizza: Mountain View','570 N Shoreline Blvd, Mountain View, CA','37.402653','-122.079354');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Tony & Alba\'s Pizza & Pasta','619 Escuela Ave, Mountain View, CA','37.394011','-122.095528');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Oregano\'s Wood-Fired Pizza','4546 El Camino Real, Los Altos, CA','37.401724','-122.114646');
下面,开始从表中查询数据。
根据latitude,longitude值,基于Haversine公式从表中查询数据。
假设我们要查询latitude=37.38714,longitude=-122.083235,范围在25英里内的前20条数据,可以这样:
SELECT id, ( 3959 * acos( cos( radians('37.38714') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('-122.083235') ) + sin( radians('37.38714') ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0, 20;
如果想使用“公里”代替“英里”,将3959换成6371即可。
特别简单。
参考资料: