経度と緯度の値を取得し、それらをMySQLクエリに入力するPHPスクリプトが動作しています。 MySQLのみにしたいと思います。現在のPHPコードは次のとおりです。
if ($distance != "Any" && $customer_Zip != "") { //get the great circle distance
//get the Origin Zip code info
$Zip_sql = "SELECT * FROM Zip_code WHERE Zip_code = '$customer_Zip'";
$result = mysql_query($Zip_sql);
$row = mysql_fetch_array($result);
$Origin_lat = $row['lat'];
$Origin_lon = $row['lon'];
//get the range
$lat_range = $distance/69.172;
$lon_range = abs($distance/(cos($details[0]) * 69.172));
$min_lat = number_format($Origin_lat - $lat_range, "4", ".", "");
$max_lat = number_format($Origin_lat + $lat_range, "4", ".", "");
$min_lon = number_format($Origin_lon - $lon_range, "4", ".", "");
$max_lon = number_format($Origin_lon + $lon_range, "4", ".", "");
$sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon' AND ";
}
誰もがこれを完全にMySQLにする方法を知っていますか?私は少しインターネットを閲覧しましたが、インターネットに関するほとんどの文献はかなり混乱しています。
From Google Code FAQ-PHP、MySQL、Google Mapsを使用したストアロケーターの作成 :
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 < 25
ORDER BY distance
LIMIT 0 , 20;
$greatCircleDistance = acos( cos($latitude0) * cos($latitude1) * cos($longitude0 - $longitude1) + sin($latitude0) * sin($latitude1));
ラジアン単位の緯度と経度を使用します。
そう
SELECT
acos(
cos(radians( $latitude0 ))
* cos(radians( $latitude1 ))
* cos(radians( $longitude0 ) - radians( $longitude1 ))
+ sin(radians( $latitude0 ))
* sin(radians( $latitude1 ))
) AS greatCircleDistance
FROM yourTable;
あなたのSQLクエリです
kmまたはマイルで結果を取得するには、結果に地球の平均半径(3959
マイル、6371
Kmまたは3440
海里)を掛けます
この例で計算しているのは境界ボックスです。座標データを 空間が有効なMySQL列 に配置する場合、 MySQLの組み込み機能 を使用してデータをクエリできます。
SELECT
id
FROM spatialEnabledTable
WHERE
MBRWithin(ogc_point, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
ヘルパーテーブルを座標テーブルに追加すると、クエリの応答時間を改善できます。
このような:
CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)
TokuDBを使用している場合、たとえば次のように述部のいずれかにクラスタリングインデックスを追加すると、パフォーマンスがさらに向上します。
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
基本的な緯度と経度が度で、sin(lat)がラジアン、cos(lat)* cos(lon)がラジアン、cos(lat)* sin(lon)がラジアンで必要になります。次に、次のようなsmsql関数を作成します。
CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
`cos_cos1` FLOAT, `cos_sin1` FLOAT,
`sin_lat2` FLOAT,
`cos_cos2` FLOAT, `cos_sin2` FLOAT)
RETURNS float
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
END
これにより距離がわかります。
Lating/lonにインデックスを追加することを忘れないでください。これにより、バウンディングボクシングが検索速度を落とす代わりに検索を支援できるようになります(インデックスは既に上記のCREATE TABLEクエリに追加されています)。
INDEX `lat_lon_idx` (`lat`, `lon`)
緯度/経度座標のみを持つ古いテーブルがある場合、次のように更新するスクリプトを設定できます:(meekrodbを使用したphp)
$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');
foreach ($users as $user)
{
$lat_rad = deg2rad($user['lat']);
$lon_rad = deg2rad($user['lon']);
DB::replace('Coordinates', array(
'object_id' => $user['id'],
'object_type' => 0,
'sin_lat' => sin($lat_rad),
'cos_cos' => cos($lat_rad)*cos($lon_rad),
'cos_sin' => cos($lat_rad)*sin($lon_rad),
'lat' => $user['lat'],
'lon' => $user['lon']
));
}
次に、実際のクエリを最適化して、実際に必要な場合にのみ距離計算を実行します。たとえば、内側と外側から円(ウェル、楕円形)をバインドします。そのためには、クエリ自体のいくつかのメトリックを事前計算する必要があります。
// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));
これらの準備を考えると、クエリは次のようになります(php):
$neighbors = DB::query("SELECT id, type, lat, lon,
geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
FROM Coordinates WHERE
lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
// center radian values: sin_lat, cos_cos, cos_sin
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
// min_lat, max_lat, min_lon, max_lon for the outside box
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
// min_lat, max_lat, min_lon, max_lon for the inside box
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
// distance in radians
$distance_rad);
上記のクエリのEXPLAINは、インデックスを使用するのに十分な結果がない限り、インデックスを使用していないと言うかもしれません。インデックスは、座標テーブルに十分なデータがあるときに使用されます。 FORCE INDEX(lat_lon_idx)をSELECTに追加して、テーブルサイズに関係なくインデックスを使用できるようにするため、EXPLAINで正しく動作していることを確認できます。
上記のコードサンプルを使用すると、最小のエラーで距離によるオブジェクト検索の実用的でスケーラブルな実装が必要になります。
私はこれをある程度詳しく調べなければならなかったので、結果を共有します。これは、Zip
テーブルとlatitude
テーブルを使用するlongitude
テーブルを使用します。 Googleマップに依存しません。むしろ、lat/longを含む任意のテーブルに適用できます。
SELECT Zip, primary_city,
latitude, longitude, distance_in_mi
FROM (
SELECT Zip, primary_city, latitude, longitude,r,
(3963.17 * ACOS(COS(RADIANS(latpoint))
* COS(RADIANS(latitude))
* COS(RADIANS(longpoint) - RADIANS(longitude))
+ SIN(RADIANS(latpoint))
* SIN(RADIANS(latitude)))) AS distance_in_mi
FROM Zip
JOIN (
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
) AS p
WHERE latitude
BETWEEN latpoint - (r / 69)
AND latpoint + (r / 69)
AND longitude
BETWEEN longpoint - (r / (69 * COS(RADIANS(latpoint))))
AND longpoint + (r / (69 * COS(RADIANS(latpoint))))
) d
WHERE distance_in_mi <= r
ORDER BY distance_in_mi
LIMIT 30
そのクエリの途中で次の行を見てください。
SELECT 42.81 AS latpoint, -70.81 AS longpoint, 50.0 AS r
これにより、緯度/経度ポイント42.81/-70.81から50.0マイル以内のZip
テーブルで最も近い30個のエントリが検索されます。これをアプリに組み込むと、そこに独自のポイントと検索範囲が設定されます。
マイルではなくキロメートルで作業する場合は、クエリで69
を111.045
に変更し、3963.17
を6378.10
に変更します。
詳細な記事は次のとおりです。私はそれが誰かを助けることを願っています。 http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
SELECT *, (
6371 * acos(cos(radians(search_lat)) * cos(radians(lat) ) *
cos(radians(lng) - radians(search_lng)) + sin(radians(search_lat)) * sin(radians(lat)))
) AS distance
FROM table
WHERE lat != search_lat AND lng != search_lng AND distance < 25
ORDER BY distance
FETCH 10 ONLY
25 kmの距離
同じことを計算できる手順を書きましたが、それぞれの表に緯度と経度を入力する必要があります。
drop procedure if exists select_lattitude_longitude;
delimiter //
create procedure select_lattitude_longitude(In CityName1 varchar(20) , In CityName2 varchar(20))
begin
declare Origin_lat float(10,2);
declare Origin_long float(10,2);
declare dest_lat float(10,2);
declare dest_long float(10,2);
if CityName1 Not In (select Name from City_lat_lon) OR CityName2 Not In (select Name from City_lat_lon) then
select 'The Name Not Exist or Not Valid Please Check the Names given by you' as Message;
else
select lattitude into Origin_lat from City_lat_lon where Name=CityName1;
select longitude into Origin_long from City_lat_lon where Name=CityName1;
select lattitude into dest_lat from City_lat_lon where Name=CityName2;
select longitude into dest_long from City_lat_lon where Name=CityName2;
select Origin_lat as CityName1_lattitude,
Origin_long as CityName1_longitude,
dest_lat as CityName2_lattitude,
dest_long as CityName2_longitude;
SELECT 3956 * 2 * ASIN(SQRT( POWER(SIN((Origin_lat - dest_lat) * pi()/180 / 2), 2) + COS(Origin_lat * pi()/180) * COS(dest_lat * pi()/180) * POWER(SIN((Origin_long-dest_long) * pi()/180 / 2), 2) )) * 1.609344 as Distance_In_Kms ;
end if;
end ;
//
delimiter ;
上記の答えについてはコメントできませんが、@ Pavel Chuchuvaの答えには注意してください。両方の座標が同じ場合、その数式は結果を返しません。その場合、距離はnullであるため、その数式では行はそのまま返されません。
私はMySQLの専門家ではありませんが、これは私のために働いているようです:
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 < 25 OR distance IS NULL ORDER BY distance LIMIT 0 , 20;
私のjavascript実装は、以下への良いリファレンスになると思いました。
/*
* Check to see if the second coord is within the precision ( meters )
* of the first coord and return accordingly
*/
function checkWithinBound(coord_one, coord_two, precision) {
var distance = 3959000 * Math.acos(
Math.cos( degree_to_radian( coord_two.lat ) ) *
Math.cos( degree_to_radian( coord_one.lat ) ) *
Math.cos(
degree_to_radian( coord_one.lng ) - degree_to_radian( coord_two.lng )
) +
Math.sin( degree_to_radian( coord_two.lat ) ) *
Math.sin( degree_to_radian( coord_one.lat ) )
);
return distance <= precision;
}
/**
* Get radian from given degree
*/
function degree_to_radian(degree) {
return degree * (Math.PI / 180);
}
mysqlで距離を計算する
SELECT (6371 * acos(cos(radians(lat2)) * cos(radians(lat1) ) * cos(radians(long1) -radians(long2)) + sin(radians(lat2)) * sin(radians(lat1)))) AS distance
したがって、距離値が計算され、必要に応じて誰でも適用できます。