使用SQL查询查找最近的纬度/经度

我有经纬度,我想从数据库中提取记录,它有最近的经纬度,如果这个距离比指定的长,就不检索它。

表结构:

id
latitude
longitude
place name
city
country
state
zip
sealevel
267383 次浏览

你正在寻找像半正矢公式这样的东西。请参见在这里

还有其他的,但这是最常被引用的。

如果您正在寻找更健壮的东西,则可能需要考虑数据库的GIS功能。它们能够做一些很酷的事情,比如告诉你一个点(城市)是否出现在给定的多边形(区域、国家、大陆)中。

听起来就像你想在距离上做一个最近邻居搜索。据我所知,SQL不支持这样的东西,你需要使用另一种数据结构,如r - treekd tree

这个问题一点也不难,但是如果你需要优化它,它就会变得更加复杂。

我的意思是,你的数据库中有100个地点还是1亿个?这有很大的不同。

如果位置的数量很小,只需执行->,就可以将它们从SQL中取出并放入代码中

Select * from Location

一旦你把它们转换成代码,用哈弗辛公式计算出每一个纬度/长度与原始值之间的距离,然后排序。

听起来你应该只使用PostGIS、SpatialLite、SQLServer2008或Oracle Spatial。它们都可以用空间SQL为您回答这个问题。

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)是开始测量距离的位置。

以防你像我一样懒,这里有一个解决方案,由这个和其他关于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的例子。

干杯

下面是我用PHP实现的完整解决方案。

此解决方案使用http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL中给出的Haversine公式。

值得注意的是,哈弗辛公式在极点处有弱点。这个答案显示了如何实现vincenty大圆距离公式来解决这个问题,但我选择只使用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);
?>

/ / 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;
}
}
?>

/ / db / dbSettings.php资产

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

根据上面“使用MySQL进行地理距离搜索”文章的建议,可以通过使用MySQL存储过程来提高性能。

我有一个约17,000个位置的数据库,查询执行时间为0.054秒。

根据文章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.

试试这个,它显示最近的点提供的坐标(50公里内)。它工作得很完美:

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/

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();
}

谷歌的解决办法:

创建表

在创建MySQL表时,需要特别注意lat和lng属性。使用谷歌Maps的当前缩放功能,您应该只需要小数点后的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中,您可以使用IMPORT选项卡导入各种文件格式,包括CSV(逗号分隔值)。Microsoft Excel和谷歌电子表格都导出为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语句。哈弗辛公式一般用于计算球面上两对坐标之间的大圆距离。维基百科给出了一个深入的数学解释,Movable Type的网站上有一个关于公式的很好的讨论,因为它与编程有关。

下面的SQL语句将查找距离坐标37 -122 25英里半径内最近的20个位置。它根据该行的纬度/经度和目标纬度/经度计算距离,然后只请求距离值小于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英里的距离内找到经纬度。

另一种方法是在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

查找离我最近的用户:

距离(米)

基于Vincenty的公式

i有用户表:

+----+-----------------------+---------+--------------+---------------+
| 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(单位:米)

在极端情况下,这种方法会失败,但为了性能考虑,我跳过了三角函数,只是简单地计算对角线的平方。

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

这个问题最初的答案是好的,但是mysql的新版本(mysql 5.7.6上)支持地理查询,所以你现在可以使用内置的功能,而不是进行复杂的查询。

你现在可以这样做:

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中使用.001而不是.000621371192(这是英里)。

MySql文档在这里 . MySql文档在这里

 +----+-----------------------+---------+--------------+---------------+
| id | email                 | name    | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 7  | test@gmail.com        | rembo   | 23.0249256   |  72.5269697   |
| 25 | test1@gmail.com.      | Rajnis  | 23.0233221    | 72.5342112   |
+----+-----------------------+---------+--------------+---------------+

$lat = 23.02350629;

$long = 72.53230239;

< p > DB:: 选择 (“; 选择 * 从 ( 选择 < em >, ( (acos(sin(";$ lat ."*π()/ 180))*罪((lat *π()/ 180))+ cos(“。美元lat !”< / em >π()/ 180))* cos ((lat *π()/ 180))* cos(((“。$ long ."- LONG) *π()/ 180))))* 180 /π())* 60 * 1.1515 * 1.609344 ) 因为距离 从 users ) 用户 在哪里 距离<= 2");

Mysql查询搜索坐标的距离限制和条件

 SELECT id, ( 3959 * acos( cos( radians('28.5850154') ) * cos( radians(latitude) ) * cos( radians( longitude ) - radians('77.07207489999999') ) + sin( radians('28.5850154') ) * sin( radians( latitude ) ) ) ) AS distance FROM `vendors` HAVING distance < 5;