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


171

我有经度和纬度,我想从数据库中提取记录,该记录具有距离中最接近的经度和纬度,如果该距离长于指定的长度,则不要检索它。

表结构:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
这有点像邻近搜索问题。
大流士培根

1
亚历山大·鲁宾 Alexander Rubin )提供了一组关于使用MySQL进行地理(邻近)搜索的幻灯片(PDF链接)
马丁·彼得

Answers:


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


49
只是一个性能说明,最好不要对距离变量进行平方,而是对“ 25”测试值求平方...稍后对需要显示距离的结果进行
平方

9
对于以米为单位的距离,相同的查询将是什么?(目前以英里为单位,对吗?)
httpete 2012年

8
那是什么尺寸25?
Steffan Donal

16
这里要澄清的是69.1是英里到纬度的转换因子。57.3大约为180 / pi,因此对于余弦函数,这是从度到弧度的转换。25是以英里为单位的搜索半径。这是使用十进制度数和法定英里数时要使用的公式。
John Vance

8
此外,它不考虑地球的曲率。对于短搜索半径,这将不是问题。否则,Evan和Igor的答案会更完整。
约翰·万斯,

62

Google的解决方案:

创建表

创建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中,您可以使用IMPORT选项卡导入各种文件格式,包括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');

使用MySQL查找位置

要在标记表中查找给定纬度/经度在一定半径范围内的位置,可以使用基于Haversine公式的SELECT语句。Haversine公式通常用于计算球体上两对坐标之间的大圆距离。Wikipedia给出了深入的数学解释,有关该公式与编程的关系的详细讨论位于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


1
是否应该在HAVING distance < 25查询25英里半径范围内的位置时进行?
Vitalii Elenhaupt

它应该> 25,然后它将搜索所有记录
vidur punj

您确定@vidurpunj> 25吗?
Amranur Ra​​hman

我使用以下方法尝试了sql查询:distance <25,但未找到任何结果..因为示例标记的距离都在25以上...
IbrahimShendy

37,-122坐标,这是我们需要查找距离的位置的经度和纬度吗?
Prasobh.Kollat​​tu,

28

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

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

应该指出的是,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);
?>

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

./assets/db/dbSettings.php

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

如上面发布的“ Geo-Distance-Search-with-MySQL”一文所建议,可以通过使用MySQL存储过程来提高性能。

我有一个〜17,000个地方的数据库,查询执行时间为0.054秒。


如何获得以公里或米为单位的距离?问候!
chemitaxis

2
英里* 1.609344 =公里
思南·迪兹达列维奇

2
警告。优秀的解决方案,但有一个错误。所有abs应删除。从度数转换为弧度时,无需获取绝对值,即使您这样做,也只对其中一种纬度进行了测量。请对其进行编辑,以修复该错误。
樟宜

1
对于任何想要以米为单位的人:将3956英里转换为公里:地球半径;转换69英里为公里:大约1度纬度的长度(以km为单位);并以公里为单位输入距离。
樟宜

1
并替换69111,044736(忘记上面的评论)
rkeet

24

以防万一您像我一样懒惰,这是从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;

1
究竟bounding_distance代表什么?此值会将结果限制为英里吗?所以在这种情况下,它将在1英里内返回结果?
james 2012年

1
@ bounding_distance在这里以度为单位,用于通过限制有效搜索区域来加快计算速度。例如,如果您知道用户在某个城市中,并且知道该城市中有几个点,则可以安全地将边界距离设置为几度。
伊万(Evan)2012年

1
这是使用哪个地理距离公式?
bbodenmiller 2015年

12

简单的一个;)

SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;

只需将坐标替换为所需的坐标即可。值必须存储为double。这是一个有效的MySQL 5.x示例。

干杯


2
不知道为什么要投票,OP希望限制和订购一定距离,而不是限制30和订购dx+dy
okm 2012年

3
这为我做到了。这不是OP想要的,而是我想要的,所以谢谢您的回答!:)
网站站长G

最外层的ABS()就足够了。
dzona '19

6

尝试此操作,它会显示到所提供坐标的最近点(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/


5

您正在寻找诸如Haversine公式之类的东西。也请参见这里

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

如果您正在寻找更强大的功能,则可能需要查看数据库的GIS功能。它们能够执行一些很酷的操作,例如告诉您某个点(城市)是否出现在给定的多边形(区域,国家/地区,大陆)内。


它的确被引用最多,但是在涉及使用其他方法进行不精确计算的陈述时,许多文章都提到了旧的计算硬件。另请参见movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan,

4

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

4

该问题的原始答案很好,但是较新版本的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代替里程使用.0001而不是里程.000621371192

MySQL文档在这里


如果可能,请在答案中添加mysql版本。
Parixit

ST_Distance_Sphere在主机的install(mysql Ver 15.1 Distrib 10.2.23-MariaDB)中不存在。我在某个地方读书代替,ST_Distance但距离遥远。
ashleedawg

@ashleedawg-从版本来看,我认为您正在使用MariaDB,它是mysql的分支。从这次对话看来,MariaDB尚未实现ST_Distance_Sphere
Sherman

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


2

查找距我最近的用户:

距离(米)

根据Vincenty的公式

我有用户表:

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


1

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


7
听起来您不应该建议人们不要切换整个数据库平台,而当我显式搜索“ Oracle”时,在我的Google搜索中显示不相关的结果...
我摔跤了一次。

1

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


1

MS SQL Edition在这里:

        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

-13

这个问题根本不是很困难,但是如果您需要对其进行优化,它将变得更加复杂。

我的意思是,您的数据库中有100个位置还是1亿个位置?这有很大的不同。

如果位置的数量很少,则只需执行->即可使其脱离SQL并进入代码

Select * from Location

将它们输入代码后,使用Haversine公式计算每个经度/纬度与原始图像之间的距离并将其排序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.