私は現在、すべての経度と緯度の情報を含むmysqlデータベースに100万個未満の場所があります。
クエリを使用して、1つのポイントと他の多くのポイント間の距離を見つけようとしています。特に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
を使用します。
Point
テーブルのGeometry
データ型のMyISAM
値を使用してポイントを作成します。 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を実行する前にできることは、側面に20単位のボックスを描く4つのポイントを作成し、そのポイントを中心に(つまり、(x1、y1))することです。 。 。 (x4、y4)、ここで(x1、y1)は(givenlong + 10単位、givenLat + 10units)です。 。 。 (givenLong-10ユニット、givenLat -10ユニット)。 実際には、左上と右下の2つのポイントだけが必要です(X1、Y1)および(X2、Y2)
現在、SQLステートメントはこれらのポイントを使用して、特定のポイントから10u以上の行を確実に除外します。緯度と経度のインデックスを使用できるため、現在よりも桁違いに高速になります。
例えば.
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ステートメントを作成できることを知っています。 MySql。しかし、私はまだC#で4つのポイントを構築し、それらをパラメーターとしてSQLクエリに渡したいと思っています。
申し訳ありませんが、MySQLとこの特定の部分に回答できる人がいたら、この回答を自由に編集してください。
良い答えについては、このプレゼンテーションを確認してください。基本的には、コメントに示されている2つの異なるアプローチを示し、どちらか一方を使用する理由とタイミング、および「ボックス内」計算が非常に興味深い理由についての詳細な説明を示します。
このブログ投稿 で、次の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 ;
使用例:緯度と経度のフィールドを持つ場所と呼ばれるテーブルを想定:
場所からのdistance_from_inputとしてget_distance_in_miles_between_geo_locations(-34.017330、22.809500、緯度、経度)を選択します。
すべて この投稿から削除
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が回答として投稿することを提案してくれたので、ここにあります。
別の方法は、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;
}
同様の問題(単一ポイントからの距離で行をフィルタリング)を解決する必要があり、元の質問と回答およびコメントを組み合わせることで、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でのジオディスタンス検索 を読んでください。これは、MySQLへのHaversine Formulaの実装に基づくソリューションです。これは、理論、実装、およびパフォーマンスの最適化を含む完全なソリューションの説明です。私の場合、空間最適化部分は正しく機能しませんでしたが。
これに2つの間違いがあることに気付きました。
p8のselectステートメントでのabs
の使用。 abs
を省略しただけで機能しました。
p27の空間検索距離関数は、ラジアンに変換したり、経度をcos(latitude)
で乗算したりしません。ただし、空間データを考慮してロードしない限り(記事のコンテキストからはわかりません)、p26の例は、空間データPOINT
がラジアンまたは度でロードされます。
これは、MySQLへのHaversine Formulaの実装に基づくソリューションであるMySQLを使用したGeo Distance Searchの非常に詳細な説明です。理論、実装、およびパフォーマンスの最適化に関するソリューションの完全な説明。私の場合、空間最適化部分は正しく機能しませんでしたが。 http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
球面投影 を使用すると、高速で単純かつ正確な(より短い距離の)近似を行うことができます。少なくとも私のルーティングアルゴリズムでは、正しい計算と比較して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);
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を使用する
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.com/a/24372831/5155484 で提案されるコメントとしてnull値を避けるために使用されます
$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";