按纬度/经度排序MySQL查询

Dan*_*Dan 13 php mysql

我的数据库中的每个用户的纬度和经度都存储在两个字段中(lat,lon)

每个字段的格式为:

lon | -1.403976 
lat | 53.428691
Run Code Online (Sandbox Code Playgroud)

如果用户搜索其他用户,例如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, '.', '');
Run Code Online (Sandbox Code Playgroud)

然后我可以执行查询,例如:

$query = "SELECT * FROM table WHERE lon BETWEEN '$minLon' AND '$maxLon' AND lat BETWEEN '$minLat' AND '$maxLat'";
Run Code Online (Sandbox Code Playgroud)

这很好用,我使用一个函数来计算和显示输出阶段用户之间的实际距离,但我希望能够通过在查询阶段减少或增加距离来对结果进行排序.

有没有办法做到这一点?

Bla*_*ger 27

记得毕达哥拉斯?

$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))";
Run Code Online (Sandbox Code Playgroud)

从技术上讲,这是距离的平方而不是实际距离,但是因为你只是用它来进行排序并不重要.

这使用了平面距离公式,该公式应该在小距离上很好.

然而:

如果您想要更精确或使用更长的距离,请使用此公式以弧度表示较大的圆距:

dist = acos[ sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lng1-lng2) ]
Run Code Online (Sandbox Code Playgroud)

(要以实际单位而不是弧度来获得距离,请将其乘以地球的半径.但这不是订购目的所必需的.)

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))";
Run Code Online (Sandbox Code Playgroud)

甚至:

$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))";
Run Code Online (Sandbox Code Playgroud)

  • 这很有趣而且很有教育意义。:-) (2认同)

MrO*_*MrO 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";
Run Code Online (Sandbox Code Playgroud)

这是一个方便的参考 - 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";
Run Code Online (Sandbox Code Playgroud)

如果您想按顺序执行并显示距离:

<?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";
Run Code Online (Sandbox Code Playgroud)

编辑@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";
Run Code Online (Sandbox Code Playgroud)

您可以轻松地将其包含在从上面提供的信息中接受Radians或Degrees的类中.


aex*_*exl 5

这是给我正确结果的公式(与上述解决方案相对)。通过使用Google地图的“测量距离”功能(直接距离而不是运输距离)进行确认。

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
Run Code Online (Sandbox Code Playgroud)

:latitude并且:longitude是PDO功能的占位符。您可以根据需要将其替换为实际值。latitudelongitude是列名。

3959是地球半径(以英里为单位);该distance输出将在英里为好。要将其更改为公里,请替换39596371