按纬度/经度排序MySQL查询

问题描述 投票:11回答:5

我的数据库中的每个用户的纬度和经度都存储在两个字段中(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'";

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

有没有办法做到这一点?

php mysql
5个回答
22
投票

记得毕达哥拉斯?

$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))";

6
投票

仅使用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的类中。


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职能的占位符。如果您愿意,可以用实际值替换它们。 latitudelongitude是列名。

3959是以英里为单位的地球半径; distance输出也将以英里为单位。要将其更改为千米,请将3959替换为6371


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);

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引用类似的问题,确实有效。

© www.soinside.com 2019 - 2024. All rights reserved.