2 つの緯度/経度ポイント間の距離を見つける最速の方法
質問
現在、mysql データベースには 100 万件弱の場所があり、すべて経度と緯度の情報が含まれています。
クエリを介して、1 つの点と他の多くの点の間の距離を見つけようとしています。特に毎秒 100 以上のヒットでは、私が望むほど速くはありません。
これには、mysql 以外に高速なクエリまたはおそらく高速なシステムはありますか?私はこのクエリを使用しています:
SELECT
name,
( 3959 * acos( cos( radians(42.290763) ) * cos( radians( locations.lat ) )
* cos( radians(locations.lng) - radians(-71.35368)) + sin(radians(42.290763))
* sin( radians(locations.lat)))) AS distance
FROM locations
WHERE active = 1
HAVING distance < 10
ORDER BY distance;
注記:指定された距離は次のとおりです マイル. 。必要な場合は キロメートル, 、 使用 6371
の代わりに 3959
.
解決
-
MyISAM
テーブルのGeometry
データ型のPoint
値を使用してポイントを作成します。 Mysql 5.7.5以降、InnoDB
テーブルはSPATIAL
インデックスもサポートするようになりました。 -
これらのポイントに
SPATIAL
インデックスを作成します -
値を見つけるには、
MBRContains()
を使用します。SELECT * FROM table WHERE MBRContains(LineFromText(CONCAT( '(' , @lon + 10 / ( 111.1 / cos(RADIANS(@lon))) , ' ' , @lat + 10 / 111.1 , ',' , @lon - 10 / ( 111.1 / cos(RADIANS(@lat))) , ' ' , @lat - 10 / 111.1 , ')' ) ,mypoint)
、または MySQL 5.1
以降:
SELECT *
FROM table
WHERE MBRContains
(
LineString
(
Point (
@lon + 10 / ( 111.1 / COS(RADIANS(@lat))),
@lat + 10 / 111.1
),
Point (
@lon - 10 / ( 111.1 / COS(RADIANS(@lat))),
@lat - 10 / 111.1
)
),
mypoint
)
これにより、ほぼボックス(@ lat +/- 10 km、@ lon +/- 10km)内のすべてのポイントが選択されます。
これは実際にはボックスではなく、球形の長方形、つまり球の緯度と経度の境界セグメントです。これは、フランツジョセフランドの単純な長方形とは異なる場合がありますが、ほとんどの居住地では非常に近いものです。
-
追加のフィルタリングを適用して、円内のすべて(正方形ではない)を選択します
-
大きな円距離(長い距離の場合)を考慮して、追加の詳細フィルタリングを適用する可能性があります
他のヒント
MySql固有の回答ではありませんが、SQLステートメントのパフォーマンスが向上します。
実際に実行しているのは、テーブル内のすべてのポイントまでの距離を計算し、特定のポイントから10単位以内にあるかどうかを確認することです。
このsqlを実行する前にできることは、1辺に20単位のボックスを描く4つのポイントを作成し、そのポイントを中央に置くことです(つまり、(x1、y1))。 。 。 (x4、y4)、ここで(x1、y1)は(givenlong + 10単位、givenLat + 10units)です。 。 。 (givenLong-10ユニット、givenLat -10ユニット)。 実際には、左上と右下の2つのポイント(X1、Y1)および(X2、Y2)のみが必要です
SQLステートメントはこれらのポイントを使用して、指定されたポイントから確実に10uを超える行を除外します。緯度&amp;のインデックスを使用できます。経度なので、現在持っているものよりも桁違いに速くなります。
e.g。
select . . .
where locations.lat between X1 and X2
and locations.Long between y1 and y2;
ボックスアプローチは誤検知を返す可能性があるため(ボックスの隅で、指定されたポイントから10u以上離れたポイントを選択できます)、各ポイントの距離を計算する必要があります。ただし、テストするポイントの数をボックス内のポイントに大幅に制限しているため、これもはるかに高速になります。
この手法を「箱の中を考える」と呼びます。 :)
編集:これを1つのSQLステートメントに入れることはできますか?
mySqlまたはPhpの機能がわかりません、申し訳ありません。 4つのポイントを構築するのに最適な場所がどこにあるのか、またはそれらをPhpのmySqlクエリに渡す方法がわからない。ただし、4つのポイントを取得したら、独自のSQLステートメントを自分のSQLステートメントと組み合わせるのを止めることはできません。
select name,
( 3959 * acos( cos( radians(42.290763) )
* cos( radians( locations.lat ) )
* cos( radians( locations.lng ) - radians(-71.35368) )
+ sin( radians(42.290763) )
* sin( radians( locations.lat ) ) ) ) AS distance
from locations
where active = 1
and locations.lat between X1 and X2
and locations.Long between y1 and y2
having distance < 10 ORDER BY distance;
MS SQLを使用して、4つのフロート(X1、Y1、X2、Y2)を宣言し、「メイン」の前にそれらを計算するSQLステートメントを作成できることを知っています。私が言ったように、selectステートメントは、これがMySqlで実行できるかどうかわかりません。ただし、C#で4つのポイントを構築し、それらをパラメーターとしてSQLクエリに渡すことは引き続き考えられます。
申し訳ありませんが、MySQL&amp;この特定の部分については、この回答を自由に編集してください。
適切な答えについては、このプレゼンテーションを確認してください。基本的には、コメントに示されている 2 つの異なるアプローチを示しており、どちらかを使用する理由やタイミング、および「インザボックス」計算が非常に興味深い理由についての詳細な説明が付いています。
on このブログ投稿では、次のMySql関数が投稿されました。私はそれをあまりテストしていませんが、投稿から収集したものから、緯度と経度のフィールドにインデックスが付けられている場合、これはあなたのためにうまくいくかもしれません:
DELIMITER $
DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $
CREATE FUNCTION get_distance_in_miles_between_geo_locations(geo1_latitude decimal(10,6), geo1_longitude decimal(10,6), geo2_latitude decimal(10,6), geo2_longitude decimal(10,6))
returns decimal(10,3) DETERMINISTIC
BEGIN
return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180) + COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180) * COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI()) * 60 * 1.1515);
END $
DELIMITER ;
使用例: 緯度&amp;フィールドを持つ場所と呼ばれるテーブルを想定経度:
get_distance_in_miles_between_geo_locations(-34.017330、 22.809500、緯度、経度)場所からのdistance_from_inputとして;
all この投稿から削除されました
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) *
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) *
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)*
pi()/180))))*180/pi())*60*1.1515 ) as distance
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X
ORDER BY ID DESC
これはMySQLのポイント間の距離計算クエリです。長いデータベースで使用しましたが、完璧に機能しています。注:要件に従って変更(データベース名、テーブル名、列など)を行ってください。
set @latitude=53.754842;
set @longitude=-2.708077;
set @radius=20;
set @lng_min = @longitude - @radius/abs(cos(radians(@latitude))*69);
set @lng_max = @longitude + @radius/abs(cos(radians(@latitude))*69);
set @lat_min = @latitude - (@radius/69);
set @lat_max = @latitude + (@radius/69);
SELECT * FROM postcode
WHERE (longitude BETWEEN @lng_min AND @lng_max)
AND (latitude BETWEEN @lat_min and @lat_max);
MySQL 5.7。*を使用している場合、 st_distance_sphere(POINT、POINT)を使用できます。
Select st_distance_sphere(POINT(-2.997065, 53.404146 ), POINT(58.615349, 23.56676 ))/1000 as distcance
select
(((acos(sin(('$latitude'*pi()/180)) * sin((`lat`*pi()/180))+cos(('$latitude'*pi()/180))
* cos((`lat`*pi()/180)) * cos((('$longitude'- `lng`)*pi()/180))))*180/pi())*60*1.1515)
AS distance
from table having distance<22;
MySQLプラグインとしてインストールする方法の詳細を含む完全なコードはこちら: https://github.com/lucasepe / lib_mysqludf_haversine
昨年、コメントとして投稿しました。親切に@TylerCollierが回答として投稿することを提案してくれたので、ここにあります。
もう1つの方法は、2点からのヘイバーシン距離を返すカスタムUDF関数を記述することです。この関数は入力を受け取ることができます:
lat1 (real), lng1 (real), lat2 (real), lng2 (real), type (string - optinal - 'km', 'ft', 'mi')
したがって、次のように記述できます。
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2) < 40;
距離が40キロメートル未満のすべてのレコードを取得します。または:
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2, 'ft') < 25;
25フィート未満の距離にあるすべてのレコードを取得します。
コア機能は次のとおりです。
double
haversine_distance( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ) {
double result = *(double*) initid->ptr;
/*Earth Radius in Kilometers.*/
double R = 6372.797560856;
double DEG_TO_RAD = M_PI/180.0;
double RAD_TO_DEG = 180.0/M_PI;
double lat1 = *(double*) args->args[0];
double lon1 = *(double*) args->args[1];
double lat2 = *(double*) args->args[2];
double lon2 = *(double*) args->args[3];
double dlon = (lon2 - lon1) * DEG_TO_RAD;
double dlat = (lat2 - lat1) * DEG_TO_RAD;
double a = pow(sin(dlat * 0.5),2) +
cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD) * pow(sin(dlon * 0.5),2);
double c = 2.0 * atan2(sqrt(a), sqrt(1-a));
result = ( R * c );
/*
* If we have a 5th distance type argument...
*/
if (args->arg_count == 5) {
str_to_lowercase(args->args[4]);
if (strcmp(args->args[4], "ft") == 0) result *= 3280.8399;
if (strcmp(args->args[4], "mi") == 0) result *= 0.621371192;
}
return result;
}
球面投影。少なくとも私のルーティングアルゴリズムでは、正しい計算と比較して20%向上します。 Javaコードでは次のようになります。
public double approxDistKm(double fromLat, double fromLon, double toLat, double toLon) {
double dLat = Math.toRadians(toLat - fromLat);
double dLon = Math.toRadians(toLon - fromLon);
double tmp = Math.cos(Math.toRadians((fromLat + toLat) / 2)) * dLon;
double d = dLat * dLat + tmp * tmp;
return R * Math.sqrt(d);
}
MySQLについてわからない(申し訳ありません!)。
制限について必ず確認してください(assertEqualsの3番目のパラメーターはキロメートル単位の精度を意味します):
float lat = 24.235f;
float lon = 47.234f;
CalcDistance dist = new CalcDistance();
double res = 15.051;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
res = 150.748;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 1, lon + 1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 1, lon + 1), 1e-2);
res = 1527.919;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 10, lon + 10), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 10, lon + 10), 10);
これは、MySQLへのHaversine Formulaの実装に基づくソリューションであるMySQLを使用したGeo Distance Searchの非常に詳細な説明です。理論、実装、およびパフォーマンスの最適化に関するソリューションの完全な説明。私の場合、空間最適化部分は正しく機能しませんでしたが。 http://www.scribd.com/doc/2569355/Geo -Distance-Search-with-MySQL
MySQLでのジオディスタンス検索、ソリューション Haversine FormulaのMySQLへの実装に基づいています。これは完全なソリューションです 理論、実装、およびパフォーマンスの最適化に関する説明。 私の場合、空間最適化部分は正しく機能しませんでしたが。
これに2つの間違いがあることに気付きました:
-
p8のselectステートメントでの
abs
の使用。abs
を省略しただけで機能しました。 -
p27の空間検索距離関数は、ラジアンに変換したり、経度を
cos(latitude)
で乗算したりしません。 )
2つの座標間のメートル数を返すMySQL関数:
CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000
異なる形式で値を返すには、関数の 6371000
を、選択した単位の地球の半径に置き換えます。たとえば、キロメートルは 6371
、マイルは 3959
です。
この関数を使用するには、MySQLの他の関数と同じように呼び出します。たとえば、テーブル city
がある場合、すべての都市から他のすべての都市までの距離を見つけることができます。
SELECT
`city1`.`name`,
`city2`.`name`,
ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
`city` AS `city1`
JOIN
`city` AS `city2`
同様の問題(単一点からの距離で行をフィルタリングする)を解決する必要があり、元の質問と回答およびコメントを組み合わせることで、MySQL 5.6と5.7の両方で完全に機能するソリューションを思い付きました。
SELECT
*,
(6371 * ACOS(COS(RADIANS(56.946285)) * COS(RADIANS(Y(coordinates)))
* COS(RADIANS(X(coordinates)) - RADIANS(24.105078)) + SIN(RADIANS(56.946285))
* SIN(RADIANS(Y(coordinates))))) AS distance
FROM places
WHERE MBRContains
(
LineString
(
Point (
24.105078 + 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 + 15 / 111.133
),
Point (
24.105078 - 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 - 15 / 111.133
)
),
coordinates
)
HAVING distance < 15
ORDER By distance
coordinates
はタイプが POINT
のフィールドで、 SPATIAL
インデックスがあります
6371
は、キロメートル単位の距離を計算するためのものです
56.946285
は中心点の緯度です
24.105078
は中心点の経度です
10
はキロメートル単位の最大距離です
私のテストでは、MySQLは coordinates
フィールドでSPATIALインデックスを使用して、四角形内のすべての行をすばやく選択し、フィルター処理されたすべての場所の実際の距離を計算して、四角形の角から場所を除外し、内部の場所のみを残しますサークル。
これは私の結果の視覚化です:
灰色の星は地図上のすべてのポイントを視覚化します。黄色の星はMySQLクエリによって返されます。四角形の角の内側(円の外側)にある灰色の星は、 MBRContains()
によって選択され、 HAVING
句によって選択解除されました。
mysqlの使用
SET @orig_lon = 1.027125;
SET @dest_lon = 1.027125;
SET @orig_lat = 2.398441;
SET @dest_lat = 2.398441;
SET @kmormiles = 6371;-- for distance in miles set to : 3956
SELECT @kmormiles * ACOS(LEAST(COS(RADIANS(@orig_lat)) *
COS(RADIANS(@dest_lat)) * COS(RADIANS(@orig_lon - @dest_lon)) +
SIN(RADIANS(@orig_lat)) * SIN(RADIANS(@dest_lat)),1.0)) as distance;
参照: https://andrew.hedges.name/experiments/haversine/
参照: https://stackoverflow.com/a/24372831/5155484
参照: http://www.plumislandmedia.net/mysql/ haversine-mysql-nearest-loc /
注: LEAST
は、 https:// stackoverflowで提案されているコメントとしてnull値を避けるために使用されます。 com / a / 24372831/5155484
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515 as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";