14

データベース内のすべてのユーザーは、緯度と経度を 2 つのフィールド (緯度、経度) に保存しています。

各フィールドの形式は次のとおりです。

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'";

これは正常に機能し、関数を使用して出力段階でユーザー間の実際の距離を計算して表示しますが、クエリ段階で距離を増減して結果を並べ替えられるようにしたいと考えています。

これを行う方法はありますか?

4

5 に答える 5

40

ピタゴラスを覚えていますか?

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

技術的には、実際の距離ではなく距離の 2 乗ですが、並べ替えに使用しているだけなので問題ありません。

これは平面距離の式を使用しており、短い距離に適しています。

でも:

より正確にしたい、またはより長い距離を使用したい場合は、ラジアンでの大円距離に次の公式を使用します。

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))";
于 2013-05-09T15:50:41.243 に答える
8

justSELECT * 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";

上記の情報から、ラジアンまたは度を受け入れるクラスにこれを簡単にまとめることができます。

于 2013-05-09T16:14:04.823 に答える