我数据库中的每个用户的纬度和经度都存储在两个字段(lat,lon)
中每个字段的格式为:
lon | -1.403976
lat | 53.428691
如果用户搜索其他用户,例如100英里,我执行以下操作以计算适当的纬度/经度范围($ lat和$ lon是当前用户的值)
$R = 3960; // earth's mean radius
$rad = '100';
// first-cut bounding box (in degrees)
$maxLat = $lat + rad2deg($rad/$R);
$minLat = $lat - rad2deg($rad/$R);
// compensate for degrees longitude getting smaller with increasing latitude
$maxLon = $lon + rad2deg($rad/$R/cos(deg2rad($lat)));
$minLon = $lon - rad2deg($rad/$R/cos(deg2rad($lat)));
$maxLat=number_format((float)$maxLat, 6, '.', '');
$minLat=number_format((float)$minLat, 6, '.', '');
$maxLon=number_format((float)$maxLon, 6, '.', '');
$minLon=number_format((float)$minLon, 6, '.', '');
然后我可以执行查询,例如:
$query = "SELECT * FROM table WHERE lon BETWEEN '$minLon' AND '$maxLon' AND lat BETWEEN '$minLat' AND '$maxLat'";
这很好用,我使用一个函数来计算和显示输出阶段用户之间的实际距离,但我希望能够通过在查询阶段减少或增加距离来对结果进行排序。
有没有办法做到这一点?
答案 0 :(得分:23)
还记得毕达哥拉斯吗?
$sql = "SELECT * FROM table
WHERE lon BETWEEN '$minLon' AND '$maxLon'
AND lat BETWEEN '$minLat' AND '$maxLat'
ORDER BY (POW((lon-$lon),2) + POW((lat-$lat),2))";
从技术上讲,这是距离的平方而不是实际距离,但是因为你只是用它进行排序并不重要。
这使用平面距离公式,它应该在小距离上很好。
<强>但是:强>
如果您想更精确或使用更长的距离,请使用this formula for great circle distances in radians:
dist = acos[ sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lng1-lng2) ]
(要以实际单位而不是弧度来获得距离,请将其乘以地球的半径。但这不是订购目的所必需的。)
MySQL计算引擎假设纬度和经度为弧度,所以如果它以度数存储(可能是),则必须将每个值乘以pi / 180,大约为0.01745:
$sf = 3.14159 / 180; // scaling factor
$sql = "SELECT * FROM table
WHERE lon BETWEEN '$minLon' AND '$maxLon'
AND lat BETWEEN '$minLat' AND '$maxLat'
ORDER BY ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))";
甚至:
$sf = 3.14159 / 180; // scaling factor
$er = 6350; // earth radius in miles, approximate
$mr = 100; // max radius
$sql = "SELECT * FROM table
WHERE $mr >= $er * ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))
ORDER BY ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))";
答案 1 :(得分:7)
仅使用SELECT * FROM Table WHERE lat between $minlat and $maxlat
将不够准确。
查询距离的正确方法是使用弧度坐标。
<?php
$sql = "SELECT * FROM Table WHERE acos(sin(1.3963) * sin(Lat) + cos(1.3963) * cos(Lat) * cos(Lon - (-0.6981))) * 6371 <= 1000";
这是一个方便的参考 - http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
例如:
<?php
$distance = 100;
$current_lat = 1.3963;
$current_lon = -0.6981;
$earths_radius = 6371;
$sql = "SELECT * FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance";
如果您想按顺序执行并显示距离:
<?php
$distance = 100;
$current_lat = 1.3963;
$current_lon = -0.6981;
$earths_radius = 6371;
$sql = "SELECT *, (acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius) as distance FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance ORDER BY acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius DESC";
为@Blazemonger编辑并避免怀疑:)如果你想以度数而不是弧度工作:
<?php
$current_lat_deg = 80.00209691585;
$current_lon_deg = -39.99818366895;
$radians_to_degs = 57.2957795;
$distance = 100;
$current_lat = $current_lat_deg / $radians_to_degs;
$current_lon = $current_lon_deg / $radians_to_degs;
$earths_radius = 6371;
$sql = "SELECT *, (acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius) as distance FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance ORDER BY acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius DESC";
您可以根据上面提供的信息轻松将其包装成接受Radians或Degrees的课程。
答案 2 :(得分:4)
这是给出正确结果的公式(与上述解决方案相反)。通过使用谷歌地图“测量距离”功能(直接距离,而不是运输距离)确认。
SELECT
*,
( 3959 * acos( cos( radians(:latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(:longitude) ) + sin( radians(:latitude) ) * sin( radians( latitude ) ) ) ) AS `distance`
FROM `locations`
ORDER BY `distance` ASC
:latitude
和:longitude
是PDO功能的占位符。如果您愿意,可以用实际值替换它们。 latitude
和longitude
是列名。
3959
是以英里为单位的地球半径; distance
输出也将以英里为单位。要将其更改为公里,请将3959
替换为6371
。
答案 3 :(得分:0)
不会给你按平面距离排序的结果(不考虑地球的曲率)但是对于小半径'应该计算出来。
SELECT * from table where lon between '$minLon' and '$maxLon' and lat between '$minLat' and '$maxLat' order by (abs(lon-$lon)/2) + (abs(lat-$lat)/2);
答案 4 :(得分:0)
以上所有答案在格林威治子午线上均无法正常工作。 Haversine公式:
// 6371 is the Earth's radius in km
6371 * 2 * ASIN(SQRT(
POWER(SIN((lat - abs(:latitude)) * pi()/180 / 2), 2)
+ COS(lat * pi()/180 ) * COS(abs(:latitude) * pi()/180)
* POWER(SIN((lon - :longitude) * pi()/180 / 2), 2)
)) as distance
我从here提取的在此answer中引用了类似的问题,确实有效。