SQLクエリで最も近い緯度/経度を見つける


173

緯度と経度があり、距離から緯度と経度が最も近いデータベースからレコードを取得します。その距離が指定した距離よりも長くなる場合は、取得しません。

テーブル構造:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
これは、近接検索の質問の複製のようなものです。
Darius Bacon

1
MySQLを使用したジオ(近接)検索での Alexander Rubinによるスライドのセット(PDFリンク)
Martijn Pieters

回答:


209
SELECT latitude, longitude, SQRT(
    POW(69.1 * (latitude - [startlat]), 2) +
    POW(69.1 * ([startlng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM TableName HAVING distance < 25 ORDER BY distance;

ここで、[starlat][startlng]は、距離の測定を開始する位置です。


49
単なるパフォーマンスノートです。距離変数を平方するのではなく、「25」テスト値を二乗するのが最善です...距離を表示する必要がある場合は、合格した結果を平方します
sradforth

9
距離をメートル単位にするための同じクエリは何ですか?(これは現在マイル
単位

8
その25は何の測定ですか?
Steffan Donal 2013

16
ここで明確にするために、69.1はマイルから緯度への変換係数です。57.3はおよそ180 / piなので、余弦関数の場合、度からラジアンへの変換になります。25はマイル単位の検索半径です。これは、小数度と法定マイルを使用するときに使用する式です。
John Vance

8
さらに、地球の曲率は考慮されません。これは、短い検索範囲では問題になりません。そうでなければ、エヴァンとイゴールの答えはより完全です。
John Vance

63

Googleのソリューション:

テーブルの作成

MySQLテーブルを作成するときは、lat属性とlng属性に特に注意を払う必要があります。Googleマップの現在のズーム機能では、小数点以下6桁の精度で十分です。テーブルに必要なストレージ領域を最小限に保つために、latおよびlng属性がサイズ(10,6)の浮動小数点数であることを指定できます。これにより、フィールドには小数点以下6桁、および小数点以下最大4桁、たとえば-123.456789度が格納されます。テーブルには、主キーとして機能するid属性も必要です。

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 ;

テーブルの作成

テーブルを作成したら、データを入力します。以下に示すサンプルデータは、全米に散らばる約180のピザ屋に関するものです。phpMyAdminでは、[インポート]タブを使用して、CSV(カンマ区切り値)などのさまざまなファイル形式をインポートできます。Microsoft ExcelとGoogleスプレッドシートはどちらもCSV形式にエクスポートされるため、CSVファイルをエクスポート/インポートすることで、スプレッドシートからMySQLテーブルにデータを簡単に転送できます。

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

MySQLを使用した場所の検索

特定の緯度/経度の特定の半径距離内にあるマーカーテーブル内の場所を見つけるには、Haversine式に基づくSELECTステートメントを使用できます。Haversine式は、一般に、球上の2組の座標間の大圏距離を計算するために使用されます。詳細な数学的な説明はWikipediaで提供されており、プログラミングに関連する公式についての良い議論はMovable Typeのサイトにあります。

37、-122座標から半径25マイル以内の最も近い20の場所を見つけるSQLステートメントを次に示します。その行の緯度/経度とターゲットの緯度/経度に基づいて距離を計算し、距離の値が25未満の行のみを求め、クエリ全体を距離順に並べ、結果を20に制限します。マイルではなくキロメートルで検索するには、3959を6371に置き換えます。

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 28 
ORDER BY distance LIMIT 0, 20;

これは、28マイル未満の距離の緯度と経度を見つけることです。

もう1つは、28〜29マイルの距離でそれらを見つけることです。

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 29 and distance > 28 
ORDER BY distance LIMIT 0, 20;

https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map


1
HAVING distance < 25半径25マイル以内の場所をクエリするのと同じですか?
Vitalii Elenhaupt 2017


@vidurpunjは25以上ですか?
Amranur Ra​​hman

距離<25を使用してSQLクエリを試しましたが、結果は見つかりませんでした..サンプルマーカーの距離がすべて25を超えているため、...
IbrahimShendy

37、-122座標、この緯度と経度は、距離を見つける必要がある場所からの位置ですか?
Prasobh.Kollat​​tu

28

PHPで実装した完全なソリューションを次に示します。

このソリューションでは、http: //www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQLに示されているHaversine式を使用しています

ハーバシンの公式は、極の周りで弱点を経験していることに注意すべきです。この回答は、これを回避するためにvincenty Great Circle Distanceの式を実装する方法を示していますが、目的に十分適しているため、Haversineのみを使用することにしました。

緯度をDECIMAL(10,8)として、経度をDECIMAL(11,8)として格納しています。うまくいけば、これが役立ちます!

showClosest.php

<?PHP
/**
 * Use the Haversine Formula to display the 100 closest matches to $origLat, $origLon
 * Only search the MySQL table $tableName for matches within a 10 mile ($dist) radius.
 */
include("./assets/db/db.php"); // Include database connection function
$db = new database(); // Initiate a new MySQL connection
$tableName = "db.table";
$origLat = 42.1365;
$origLon = -71.7559;
$dist = 10; // This is the maximum distance (in miles) away from $origLat, $origLon in which to search
$query = "SELECT name, latitude, longitude, 3956 * 2 * 
          ASIN(SQRT( POWER(SIN(($origLat - latitude)*pi()/180/2),2)
          +COS($origLat*pi()/180 )*COS(latitude*pi()/180)
          *POWER(SIN(($origLon-longitude)*pi()/180/2),2))) 
          as distance FROM $tableName WHERE 
          longitude between ($origLon-$dist/cos(radians($origLat))*69) 
          and ($origLon+$dist/cos(radians($origLat))*69) 
          and latitude between ($origLat-($dist/69)) 
          and ($origLat+($dist/69)) 
          having distance < $dist ORDER BY distance limit 100"; 
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)) {
    echo $row['name']." > ".$row['distance']."<BR>";
}
mysql_close($db);
?>

./assets/db/db.php

<?PHP
/**
 * Class to initiate a new MySQL connection based on $dbInfo settings found in dbSettings.php
 *
 * @example $db = new database(); // Initiate a new database connection
 * @example mysql_close($db); // close the connection
 */
class database{
    protected $databaseLink;
    function __construct(){
        include "dbSettings.php";
        $this->database = $dbInfo['host'];
        $this->mysql_user = $dbInfo['user'];
        $this->mysql_pass = $dbInfo['pass'];
        $this->openConnection();
        return $this->get_link();
    }
    function openConnection(){
    $this->databaseLink = mysql_connect($this->database, $this->mysql_user, $this->mysql_pass);
    }

    function get_link(){
    return $this->databaseLink;
    }
}
?>

./assets/db/dbSettings.php

<?php
$dbInfo = array(
    'host'      => "localhost",
    'user'      => "root",
    'pass'      => "password"
);
?>

上記の「Geo-Distance-Search-with-MySQL」の記事で提案されているように、MySQLストアドプロシージャを使用してパフォーマンスを向上させることができる場合があります。

約17,000か所のデータベースがあり、クエリの実行時間は0.054秒です。


距離をキロメートルまたはメートルで取得するにはどうすればよいですか?よろしく!
走化性2014年

2
マイル* 1.609344 = km
SinanDizdarević2015年

2
警告。優れたソリューションですが、バグがあります。すべてをabs削除する必要があります。度からラジアンに変換するときにabs値を取得する必要はありません。実行したとしても、緯度の1つに変換することになります。バグを修正するように編集してください。
チャンゴ2016年

1
そして、これをメートル単位で計算したい人のために:3956マイルをキロメートルに変換:地球の半径。69マイルをキロメートルに変換します:1度の緯度のおよその長さ(km)。距離をキロメートル単位で入力します。
チャンゴ2016年

1
そして、置き換え69111,044736(忘れてしまったことは、上記のコメントで)
rkeet

24

あなたが私のように怠惰な場合に備えて、これがSOと他の答えから融合した解決策です。

set @orig_lat=37.46; 
set @orig_long=-122.25; 
set @bounding_distance=1;

SELECT
*
,((ACOS(SIN(@orig_lat * PI() / 180) * SIN(`lat` * PI() / 180) + COS(@orig_lat * PI() / 180) * COS(`lat` * PI() / 180) * COS((@orig_long - `long`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance` 
FROM `cities` 
WHERE
(
  `lat` BETWEEN (@orig_lat - @bounding_distance) AND (@orig_lat + @bounding_distance)
  AND `long` BETWEEN (@orig_long - @bounding_distance) AND (@orig_long + @bounding_distance)
)
ORDER BY `distance` ASC
limit 25;

1
正確には何をbounding_distance表していますか?この値は結果をマイル単位に制限しますか?この場合、1マイル以内に結果が返されますか?
ジェームズ

1
@ bounding_distanceはここでは度単位であり、有効な検索領域を制限することで計算を高速化するために使用されます。たとえば、ユーザーが特定の都市にいることがわかっていて、その都市内にいくつかのポイントがあることがわかっている場合は、安全に境界距離を数度に設定できます。
エヴァン

1
これはどの地理的距離の式を使用していますか?
bbodenmiller 2015

12

簡単なもの;)

SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;

座標を必要な座標に置き換えるだけです。値はdoubleとして格納する必要があります。これは動作するMySQL 5.xの例です。

乾杯


2
OPがなぜ賛成票を投じる理由がわからない、OPが30までに制限せずに特定の距離によって制限および順序付けしたいdx+dy
okm

3
これでうまくいきました。それはOPが望んでいたものではありませんが、私が欲しかったものです。:)
ウェブマスターG

最も外側のABS()で十分です。
dzona

6

これを試してください。指定された座標(50 km以内)に最も近い点が表示されます。それは完全に機能します:

SELECT m.name,
    m.lat, m.lon,
    p.distance_unit
             * DEGREES(ACOS(COS(RADIANS(p.latpoint))
             * COS(RADIANS(m.lat))
             * COS(RADIANS(p.longpoint) - RADIANS(m.lon))
             + SIN(RADIANS(p.latpoint))
             * SIN(RADIANS(m.lat)))) AS distance_in_km
FROM <table_name> AS m
JOIN (
      SELECT <userLat> AS latpoint, <userLon> AS longpoint,
             50.0 AS radius, 111.045 AS distance_unit
     ) AS p ON 1=1
WHERE m.lat
BETWEEN p.latpoint  - (p.radius / p.distance_unit)
    AND p.latpoint  + (p.radius / p.distance_unit)
    AND m.lon BETWEEN p.longpoint - (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
    AND p.longpoint + (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
ORDER BY distance_in_km

ただ変える<table_name><userLat>そして<userLon>

このソリューションの詳細については、http//www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/を参照してください。


5

あなたはhaversine式のようなものを探しています。こちらもご覧ください。

他にもありますが、これが最も一般的に引用されています。

さらに堅牢なものを探している場合は、データベースのGIS機能を確認することをお勧めします。ポイント(市)が特定のポリゴン(地域、国、大陸)内に表示されるかどうかを伝えるなど、いくつかの優れた機能を備えています。


それは確かに最も引用されていますが、他の方法を使用した不正確なコンピューティングに関する記述に関しては、多くの記事が古いコンピューティングハードウェアに言及しています。また、movable-type.co.uk / scripts / latlong.html#cosine
Arjan

5

質問に対する元の回答は適切ですが、mysqlの新しいバージョン(MySQL 5.7.6以降)はgeoクエリをサポートしているため、複雑なクエリを実行する代わりに組み込みの機能を使用できます。

次のようなことができます:

select *, ST_Distance_Sphere( point ('input_longitude', 'input_latitude'), 
                              point(longitude, latitude)) * .000621371192 
          as `distance_in_miles` 
  from `TableName`
having `distance_in_miles` <= 'input_max_distance'
 order by `distance_in_miles` asc

結果が返されるmetersのでKM、マイルでは.0001なく使用したい場合は、.000621371192

MySqlドキュメントはこちら


可能であれば、mysqlバージョンを回答に追加してください。
Parixit 2018年

ST_Distance_Sphereホストのインストール(mysql Ver 15.1 Distrib 10.2.23-MariaDB)に存在しません。どこか読み替えて代用しますST_Distanceが、距離はかなりずれています。
ashleedawg

@ashleedawg-バージョンから、MySQLのフォークであるMariaDBを使用していると思います。この会話から MariaDBは実装されていないようですST_Distance_Sphere
シャーマン

4

記事Geo-Distance-Search-with-MySQLに基づいてこのコードを確認してください:

例:現在地から半径10マイル以内の最も近い10のホテルを検索します。

#Please notice that (lat,lng) values mustn't be negatives to perform all calculations

set @my_lat=34.6087674878572; 
set @my_lng=58.3783670308302;
set @dist=10; #10 miles radius

SELECT dest.id, dest.lat, dest.lng,  3956 * 2 * ASIN(SQRT(POWER(SIN((@my_lat -abs(dest.lat)) * pi()/180 / 2),2) + COS(@my_lat * pi()/180 ) * COS(abs(dest.lat) *  pi()/180) * POWER(SIN((@my_lng - abs(dest.lng)) *  pi()/180 / 2), 2))
) as distance
FROM hotel as dest
having distance < @dist
ORDER BY distance limit 10;

#Also notice that distance are expressed in terms of radius.

3
simpledb.execSQL("CREATE TABLE IF NOT EXISTS " + tablename + "(id INTEGER PRIMARY KEY   AUTOINCREMENT,lat double,lng double,address varchar)");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2891001','70.780154','craftbox');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2901396','70.7782428','kotecha');");//22.2904718 //70.7783906
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2863155','70.772108','kkv Hall');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.275993','70.778076','nana mava');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2667148','70.7609386','Govani boys hostal');");


    double curentlat=22.2667258;  //22.2677258
    double curentlong=70.76096826;//70.76096826

    double curentlat1=curentlat+0.0010000;
    double curentlat2=curentlat-0.0010000;

    double curentlong1=curentlong+0.0010000;
    double curentlong2=curentlong-0.0010000;

    try{

        Cursor c=simpledb.rawQuery("select * from '"+tablename+"' where (lat BETWEEN '"+curentlat2+"' and '"+curentlat1+"') or (lng BETWEEN         '"+curentlong2+"' and '"+curentlong1+"')",null);

        Log.d("SQL ", c.toString());
        if(c.getCount()>0)
        {
            while (c.moveToNext())
            {
                double d=c.getDouble(1);
                double d1=c.getDouble(2);

            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }


2

私に最も近いユーザーを探す:

メートル単位の距離

ヴィンセンティの公式に基づいています

ユーザーテーブルがあります。

+----+-----------------------+---------+--------------+---------------+
| id | email                 | name    | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | xxxxxx@xxxxxxxxxx.com | Isaac   | 17.2675625   | -97.6802361   |
| 14 | xxxx@xxxxxxx.com.mx   | Monse   | 19.392702    | -99.172596    |
+----+-----------------------+---------+--------------+---------------+

sql:

-- my location:  lat   19.391124   -99.165660
SELECT 
(ATAN(
    SQRT(
        POW(COS(RADIANS(users.location_lat)) * SIN(RADIANS(users.location_long) - RADIANS(-99.165660)), 2) +
        POW(COS(RADIANS(19.391124)) * SIN(RADIANS(users.location_lat)) - 
       SIN(RADIANS(19.391124)) * cos(RADIANS(users.location_lat)) * cos(RADIANS(users.location_long) - RADIANS(-99.165660)), 2)
    )
    ,
    SIN(RADIANS(19.391124)) * 
    SIN(RADIANS(users.location_lat)) + 
    COS(RADIANS(19.391124)) * 
    COS(RADIANS(users.location_lat)) * 
    COS(RADIANS(users.location_long) - RADIANS(-99.165660))
 ) * 6371000) as distance,
users.id
FROM users
ORDER BY distance ASC

地球の半径:6371000(メートル)


1

MS SQLエディションはこちら:

        DECLARE @SLAT AS FLOAT
        DECLARE @SLON AS FLOAT

        SET @SLAT = 38.150785
        SET @SLON = 27.360249

        SELECT TOP 10 [LATITUDE], [LONGITUDE], SQRT(
            POWER(69.1 * ([LATITUDE] - @SLAT), 2) +
            POWER(69.1 * (@SLON - [LONGITUDE]) * COS([LATITUDE] / 57.3), 2)) AS distance
        FROM [TABLE] ORDER BY 3

0

PostGIS、SpatialLite、SQLServer2008、またはOracle Spatialを使用する必要があるようです。彼らはすべて、空間SQLでこの質問に答えることができます。


7
あなたのような音だけでは、人々は彼らの全体のデータベースプラットフォームを切り替える示唆し、私は明示的にあちこちの「Oracle」を検索したとき、私のGoogle検索に表示するために無関係な結果が発生することはありません...
私がクマを一度苦闘しました。

0

極端な場合、このアプローチは失敗しますが、パフォーマンスのために、三角法をスキップして単純に対角二乗を計算しました。


-13

この問題はそれほど難しくありませんが、最適化する必要がある場合はさらに複雑になります。

つまり、データベースに100か所、または1億か所ありますか?それは大きな違いを生みます。

場所の数が少ない場合は、SQLからそれらを取り出して、コードを作成します->

Select * from Location

それらをコードに入れたら、Haversine式を使用して各緯度/経度とオリジナルの距離を計算し、並べ替えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.