纬度/经度找到最近的纬度/经度 - 复杂的sql或复杂的计算

Bas*_*sit 163 mysql sql math coordinates computational-geometry

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

表结构:

id
latitude
longitude
place name
city
country
state
zip
sealevel
Run Code Online (Sandbox Code Playgroud)

Kal*_*tha 191

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;
Run Code Online (Sandbox Code Playgroud)

其中[starlat][startlng]是开始测量距离的位置.

  • 只是一个性能注释,最好不要对距离变量进行sqrt,而是将"25"测试值平方...稍后如果你需要显示距离,则通过结果 (48认同)
  • 只是为了澄清这里69.1是英里到纬度的转换因子.对于余弦函数,57.3大约为180/pi,因此从度数到弧度的转换.25是以英里为单位的搜索半径.这是使用十进制度数和法定里程时使用的公式. (15认同)
  • 距离以米为单位的查询是什么?(目前在几英里,对吗?) (8认同)
  • 25那是什么衡量标准? (8认同)
  • 另外,它没有考虑地球的曲率.对于短搜索半径,这不是问题.否则Evan和Igor的答案会更完整. (8认同)
  • @PatrickBassut,我知道这已经晚了,但对于其他人来说,如果你需要你的"AS距离"以公里为单位,你只需要Km /地球度.由于Kaletha已经给你69.1英里/度,那么变化将是~111.2km.[描述](http://geography.about.com/library/faq/blqzdistancedegree.htm).另外,仅仅是FYI,如上所述,不要摆弄57.3值,它是度数与弧度的转换因子. (4认同)
  • 在 Postgres 上,只有当我删除 HAVING 子句时它才有效。`错误:列距离不存在`。为什么? (2认同)

Igo*_*aka 132

您需要的是将距离转换为经度和纬度,根据大致在边界框中的条目进行过滤,然后进行更精确的距离过滤.这是一篇很好的论文,解释了如何做到这一切:

http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

  • 非常好的文档,请注意:在Km中使用它只需将地球半径单位从英里转换为公里(3956 - > 6371) (2认同)

Svi*_*siv 55

谷歌的解决方案:

创建表

创建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 ;
Run Code Online (Sandbox Code Playgroud)

填充表格

创建表后,是时候用数据填充它了.下面提供的样本数据是散布在美国各地的大约180个pizzarias.在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');
Run Code Online (Sandbox Code Playgroud)

使用MySQL查找位置

要在标记表中查找位于给定纬度/经度的特定半径范围内的位置,可以使用基于Haversine公式的SELECT语句.Haversine公式通常用于计算球体上两对坐标之间的大圆距离.维基百科给出了一个深入的数学解释,并且关于编程的一个很好的讨论是在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;
Run Code Online (Sandbox Code Playgroud)

https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map


cir*_*try 28

这是我在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).希望这有帮助!

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);
?>
Run Code Online (Sandbox Code Playgroud)

./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;
    }
}
?>
Run Code Online (Sandbox Code Playgroud)

./assets/db/dbSettings.php

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

通过使用上面发布的"Geo-Distance-Search-with-MySQL"文章所建议的MySQL存储过程,可以提高性能.

我有一个~17,000个地方的数据库,查询执行时间是0.054秒.

  • 英里*1.609344 = km (2认同)
  • 警告.优秀的解决方案,但它有一个错误.应删除所有`abs`.从度数转换为弧度时无需获取abs值,即使你这样做,也只是在其中一个纬度上进行.请编辑它以便修复错误. (2认同)

Eva*_*van 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;
Run Code Online (Sandbox Code Playgroud)


She*_*man 15

该问题的原始答案很好,但较新版本的 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
Run Code Online (Sandbox Code Playgroud)

结果在 中返回meters。所以如果你想KM简单地使用.001而不是.000621371192(这是英里)。

MySql 文档在这里


小智 12

容易一个;)

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

只需用您需要的坐标替换坐标.值必须存储为double.这是一个有效的MySQL 5.x示例.

干杯

  • 这样做对我来说.这不是OP想要的,但这是我想要的,所以谢谢你的回答!:) (3认同)
  • 不知道为什么要投票,OP希望以一定的距离限制和排序,而不是以30限制并且以`dx + dy`限制 (2认同)

sma*_*use 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
Run Code Online (Sandbox Code Playgroud)

只是改变<table_name><userLat><userLon>

您可以在此处阅读有关此解决方案的更多信息:http : //www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


Koo*_*obz 5

你正在寻找像胡萝卜素配方这样的东西.在这里也可以看到.

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

如果您正在寻找更强大的功能,您可能需要查看数据库GIS功能.他们能够做一些很酷的事情,比如告诉你一个点(城市)是否出现在一个给定的多边形(Region,Country,Continent)中.