緯度と経度があり、距離に最も近い緯度と経度を持つデータベースからレコードを取得します。その距離が指定された距離より長くなった場合、取得しません。
テーブル構造:
id
latitude
longitude
place name
city
country
state
Zip
sealevel
必要なのは、距離を経度と緯度に変換し、それらに基づいてフィルタリングして、大まかにバウンディングボックスにあるエントリをバインドし、より正確な距離フィルターを実行することです。これをすべて行う方法を説明した素晴らしい論文があります:
http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
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]は、距離の測定を開始する位置です。
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 Spreadsheetsは両方とも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');
特定の緯度/経度から特定の半径距離内にあるマーカーテーブル内の場所を見つけるには、Haversin式に基づいたSELECTステートメントを使用できます。 Haversine公式は、一般に、球上の2組の座標間の大圏距離の計算に使用されます。詳細な数学的な説明はウィキペディアによって与えられ、プログラミングに関連する式の良い議論は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 < 25
ORDER BY distance LIMIT 0, 20;
https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map
PHPに実装された私の完全なソリューションを次に示します。
このソリューションでは、 http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL に記載されているHaversine式を使用します。
Haversineフォーミュラには極付近で弱点があることに注意してください。 この回答 は、これを回避するために vincenty Great Circle Distanceの式 を実装する方法を示していますが、私はHaversineを使用することを選択しました。
緯度をDECIMAL(10,8)として、経度をDECIMAL(11,8)として保存しています。これがお役に立てば幸いです!
<?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);
?>
<?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;
}
}
?>
<?php
$dbInfo = array(
'Host' => "localhost",
'user' => "root",
'pass' => "password"
);
?>
上記の「Geo-Distance-Search-with-MySQL」の記事で提案されているように、MySQLストアドプロシージャを使用することにより、パフォーマンスを向上させることができる場合があります。
〜17,000の場所のデータベースがあり、クエリの実行時間は0.054秒です。
あなたが私のように怠け者である場合に備えて、これと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;
簡単なもの;)
SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;
座標を必要な座標に置き換えるだけです。値はdoubleとして保存する必要があります。これは、動作するMySQL 5.xの例ではありません。
乾杯
haversine formula のようなものを探しています。 here も参照してください。
他にもありますが、これが最も一般的に引用されています。
さらに堅牢なものを探している場合は、データベースのGIS機能を調べてください。特定のポリゴン(地域、国、大陸)内にポイント(都市)が表示されるかどうかを伝えるなど、いくつかのクールな機能があります。
これを試してください。指定された座標(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/
記事に基づいてこのコードを確認してください 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.
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();
}
my:に最も近いユーザーを見つける
メートル単位の距離
Vincentyの式 に基づく
ユーザーテーブルがあります。
+----+-----------------------+---------+--------------+---------------+
| id | email | name | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | [email protected] | Isaac | 17.2675625 | -97.6802361 |
| 14 | [email protected] | 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(メートル)
PostGIS、SpatialLite、SQLServer2008、またはOracle Spatialを使用するだけのように聞こえます。空間SQLを使用して、この質問にすべて回答できます。
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
極端な場合、このアプローチは失敗しますが、パフォーマンスのために、三角法をスキップし、単純に対角の2乗を計算しました。
質問に対する元の回答は良いですが、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
を使用します。