237

我目前在 mysql 数据库中只有不到一百万个位置,所有位置都包含经度和纬度信息。

我试图通过查询找到一个点与许多其他点之间的距离。它没有我想要的那么快,尤其是每秒点击 100 次以上。

除了 mysql 之外,是否有更快的查询或可能更快的系统?我正在使用这个查询:

SELECT 
  name, 
   ( 3959 * acos( cos( radians(42.290763) ) * cos( radians( locations.lat ) ) 
   * cos( radians(locations.lng) - radians(-71.35368)) + sin(radians(42.290763)) 
   * sin( radians(locations.lat)))) AS distance 
FROM locations 
WHERE active = 1 
HAVING distance < 10 
ORDER BY distance;

注意:提供的距离以英里为单位。如果您需要公里,请使用6371而不是3959.

4

15 回答 15

120
  • 使用表中数据类型的Point值创建您的点。从 Mysql 5.7.5开始,表现在也支持索引。GeometryMyISAMInnoDBSPATIAL

  • 在这些点上创建SPATIAL索引

  • 用于MBRContains()查找值:

      SELECT  *
      FROM    table
      WHERE   MBRContains(LineFromText(CONCAT(
              '('
              , @lon + 10 / ( 111.1 / cos(RADIANS(@lat)))
              , ' '
              , @lat + 10 / 111.1
              , ','
              , @lon - 10 / ( 111.1 / cos(RADIANS(@lat)))
              , ' '
              , @lat - 10 / 111.1 
              , ')' )
              ,mypoint)
    

, 或, 在MySQL 5.1及以上:

    SELECT  *
    FROM    table
    WHERE   MBRContains
                    (
                    LineString
                            (
                            Point (
                                    @lon + 10 / ( 111.1 / COS(RADIANS(@lat))),
                                    @lat + 10 / 111.1
                                  ),
                            Point (
                                    @lon - 10 / ( 111.1 / COS(RADIANS(@lat))),
                                    @lat - 10 / 111.1
                                  ) 
                            ),
                    mypoint
                    )

这将选择框内大约所有的点(@lat +/- 10 km, @lon +/- 10km)

这实际上不是一个盒子,而是一个球面矩形:球体的经纬线段。这可能与弗朗兹约瑟夫地的普通矩形不同,但在大多数有人居住的地方都非常接近。

  • 应用额外的过滤来选择圆圈内的所有内容(不是正方形)

  • 可能应用额外的精细过滤来解决大圆距离(对于大距离)

于 2009-06-17T12:25:00.383 回答
103

不是 MySql 特定的答案,但它会提高你的 sql 语句的性能。

您实际上正在做的是计算到表中每个点的距离,看看它是否在给定点的 10 个单位内。

在运行此 sql 之前,您可以做的是创建四个点,在一边绘制一个 20 个单位的框,您的点在中心,即。(x1,y1)。. . (x4, y4),其中 (x1,y1) 是 (givenlong + 10 个单位,givenLat + 10units) 。. . (givenLong - 10 个单位,givenLat -10 个单位)。 实际上,你只需要两个点,左上角和右下角分别称为 (X1, Y1) 和 (X2, Y2)

现在,您的 SQL 语句使用这些点来排除距离您给定点绝对超过 10u 的行,它可以使用纬度和经度上的索引,因此将比您目前拥有的快几个数量级。

例如

select . . . 
where locations.lat between X1 and X2 
and   locations.Long between y1 and y2;

盒子方法可能会返回误报(您可以在盒子的角落拾取距离给定点 > 10u 的点),因此您仍然需要计算每个点的距离。但是,这又会快得多,因为您已将要测试的点数大大限制为框内的点。

我称这种技术为“在盒子里思考”:)

编辑:这可以放在一个 SQL 语句中吗?

我不知道 mySql 或 Php 能做什么,抱歉。我不知道构建这四个点的最佳位置在哪里,也不知道如何将它们传递给 PHP 中的 mySql 查询。但是,一旦您掌握了这四点,就没有什么能阻止您将自己的 SQL 语句与我的结合起来了。

select name, 
       ( 3959 * acos( cos( radians(42.290763) ) 
              * cos( radians( locations.lat ) ) 
              * cos( radians( locations.lng ) - radians(-71.35368) ) 
              + sin( radians(42.290763) ) 
              * sin( radians( locations.lat ) ) ) ) AS distance 
from locations 
where active = 1 
and locations.lat between X1 and X2 
and locations.Long between y1 and y2
having distance < 10 ORDER BY distance;

我知道使用 MS SQL 我可以构建一个声明四个浮点数(X1、Y1、X2、Y2)并在“主”选择语句之前计算它们的 SQL 语句,就像我说的那样,我不知道这是否可以用mysql。但是,我仍然倾向于在 C# 中构建这四个点并将它们作为参数传递给 SQL 查询。

抱歉,我无法提供更多帮助,如果有人可以回答 MySQL 和 Php 的特定部分,请随时编辑此答案。

于 2009-06-17T12:42:41.753 回答
25

我需要解决类似的问题(按与单点的距离过滤行)并将原始问题与答案和评论相结合,我想出了在 MySQL 5.6 和 5.7 上都非常适合我的解决方案。

SELECT 
    *,
    (6371 * ACOS(COS(RADIANS(56.946285)) * COS(RADIANS(Y(coordinates))) 
    * COS(RADIANS(X(coordinates)) - RADIANS(24.105078)) + SIN(RADIANS(56.946285))
    * SIN(RADIANS(Y(coordinates))))) AS distance
FROM places
WHERE MBRContains
    (
    LineString
        (
        Point (
            24.105078 + 15 / (111.320 * COS(RADIANS(56.946285))),
            56.946285 + 15 / 111.133
        ),
        Point (
            24.105078 - 15 / (111.320 * COS(RADIANS(56.946285))),
            56.946285 - 15 / 111.133
        )
    ),
    coordinates
    )
HAVING distance < 15
ORDER By distance

coordinates是具有类型的字段POINT并且具有SPATIAL索引
6371用于计算距离(以公里
56.946285为单位) 中心点的纬度 中心点
24.105078的经度 以
15公里为单位的最大距离

在我的测试中,MySQL 使用coordinates字段上的空间索引来快速选择矩形内的所有行,然后计算所有过滤位置的实际距离,以排除矩形角的位置,只留下圆圈内的位置。

这是我的结果的可视化:

地图

灰色星星可视化地图上的所有点,黄色星星是 MySQL 查询返回的点。矩形角内(但在圆形外)的灰色星由子句选择MBRContains()然后取消选择。HAVING

于 2018-08-20T14:27:52.097 回答
14

以下 MySQL 函数已发布在此博客文章中。我没有对其进行太多测试,但从我从帖子中收集到的信息来看,如果您的纬度和经度字段被索引,这可能对您有用:

DELIMITER $$

DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $$
CREATE FUNCTION get_distance_in_miles_between_geo_locations(
  geo1_latitude decimal(10,6), geo1_longitude decimal(10,6), 
  geo2_latitude decimal(10,6), geo2_longitude decimal(10,6)) 
returns decimal(10,3) DETERMINISTIC
BEGIN
  return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180) 
    + COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180) 
    * COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI()) 
    * 60 * 1.1515);
END $$

DELIMITER ;

示例用法:

假设一个places用字段latitude&调用的表longitude

SELECT get_distance_in_miles_between_geo_locations(-34.017330, 22.809500,
latitude, longitude) AS distance_from_input FROM places;
于 2012-06-11T17:41:54.030 回答
12

如果您使用的是 MySQL 5.7.*,那么您可以使用st_distance_sphere(POINT, POINT)

Select st_distance_sphere(POINT(-2.997065, 53.404146 ), POINT(58.615349, 23.56676 ))/1000  as distcance
于 2017-11-07T14:12:49.073 回答
9
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) * 
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) * 
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)* 
pi()/180))))*180/pi())*60*1.1515 ) as distance 
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X 
ORDER BY ID DESC

这是MySQL中到点之间的距离计算查询,我在一个长数据库中使用它,它工作得很好!注意:根据您的要求进行更改(数据库名称、表名称、列等)。

于 2014-10-14T08:20:25.260 回答
8
set @latitude=53.754842;
set @longitude=-2.708077;
set @radius=20;

set @lng_min = @longitude - @radius/abs(cos(radians(@latitude))*69);
set @lng_max = @longitude + @radius/abs(cos(radians(@latitude))*69);
set @lat_min = @latitude - (@radius/69);
set @lat_max = @latitude + (@radius/69);

SELECT * FROM postcode
WHERE (longitude BETWEEN @lng_min AND @lng_max)
AND (latitude BETWEEN @lat_min and @lat_max);

来源

于 2013-04-27T05:50:37.573 回答
7
   select
   (((acos(sin(('$latitude'*pi()/180)) * sin((`lat`*pi()/180))+cos(('$latitude'*pi()/180)) 
    * cos((`lat`*pi()/180)) * cos((('$longitude'- `lng`)*pi()/180))))*180/pi())*60*1.1515) 
    AS distance
    from table having distance<22;
于 2014-04-22T06:03:47.060 回答
5

一个 MySQL 函数,它返回两个坐标之间的米数:

CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000

要以不同的格式返回值,请将6371000函数中的 替换为您选择的单位中的地球半径。例如,km 是6371,miles 是3959

要使用该函数,只需像调用 MySQL 中的任何其他函数一样调用它。例如,如果你有一张桌子city,你可以找到每个城市到其他城市的距离:

SELECT
    `city1`.`name`,
    `city2`.`name`,
    ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
    `city` AS `city1`
JOIN
    `city` AS `city2`
于 2016-03-03T09:57:24.090 回答
4

有关如何安装为 MySQL 插件的详细信息的完整代码在这里:https ://github.com/lucasepe/lib_mysqludf_haversine

我去年发布了这个作为评论。由于好心@TylerCollier 建议我发布作为答案,就在这里。

另一种方法是编写一个自定义 UDF 函数,该函数返回两点的半正弦距离。这个函数可以接受输入:

lat1 (real), lng1 (real), lat2 (real), lng2 (real), type (string - optinal - 'km', 'ft', 'mi')

所以我们可以这样写:

SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2) < 40;

获取距离小于 40 公里的所有记录。或者:

SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2, 'ft') < 25;

获取距离小于 25 英尺的所有记录。

核心功能是:

double
haversine_distance( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ) {
    double result = *(double*) initid->ptr;
    /*Earth Radius in Kilometers.*/ 
    double R = 6372.797560856;
    double DEG_TO_RAD = M_PI/180.0;
    double RAD_TO_DEG = 180.0/M_PI;
    double lat1 = *(double*) args->args[0];
    double lon1 = *(double*) args->args[1];
    double lat2 = *(double*) args->args[2];
    double lon2 = *(double*) args->args[3];
    double dlon = (lon2 - lon1) * DEG_TO_RAD;
    double dlat = (lat2 - lat1) * DEG_TO_RAD;
    double a = pow(sin(dlat * 0.5),2) + 
        cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD) * pow(sin(dlon * 0.5),2);
    double c = 2.0 * atan2(sqrt(a), sqrt(1-a));
    result = ( R * c );
    /*
     * If we have a 5th distance type argument...
     */
    if (args->arg_count == 5) {
        str_to_lowercase(args->args[4]);
        if (strcmp(args->args[4], "ft") == 0) result *= 3280.8399;
        if (strcmp(args->args[4], "mi") == 0) result *= 0.621371192;
    }

    return result;
}
于 2014-08-16T10:10:52.100 回答
3

可以使用球面投影进行快速、简单和准确(对于更小距离)的近似。至少在我的路由算法中,与正确计算相比,我得到了 20% 的提升。在 Java 代码中,它看起来像:

public double approxDistKm(double fromLat, double fromLon, double toLat, double toLon) {
    double dLat = Math.toRadians(toLat - fromLat);
    double dLon = Math.toRadians(toLon - fromLon);
    double tmp = Math.cos(Math.toRadians((fromLat + toLat) / 2)) * dLon;
    double d = dLat * dLat + tmp * tmp;
    return R * Math.sqrt(d);
}

不确定 MySQL(对不起!)。

确保您了解限制(assertEquals 的第三个参数表示以公里为单位的精度):

    float lat = 24.235f;
    float lon = 47.234f;
    CalcDistance dist = new CalcDistance();
    double res = 15.051;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);

    res = 150.748;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 1, lon + 1), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 1, lon + 1), 1e-2);

    res = 1527.919;
    assertEquals(res, dist.calcDistKm(lat, lon, lat - 10, lon + 10), 1e-3);
    assertEquals(res, dist.approxDistKm(lat, lon, lat - 10, lon + 10), 10);
于 2012-06-29T11:59:34.247 回答
3

这是一个非常详细的关于使用 MySQL 进行地理距离搜索的描述,这是一个基于对 mysql 执行 Haversine 公式的解决方案。包含理论、实现和进一步性能优化的完整解决方案描述。尽管在我的情况下空间优化部分无法正常工作。 http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

于 2013-11-21T20:40:34.317 回答
3

阅读使用 MySQL 进行地理距离搜索,这是一种基于对 MySQL 实施 Haversine 公式的解决方案。这是一个完整的解决方案描述,包括理论、实现和进一步的性能优化。尽管在我的情况下空间优化部分无法正常工作。

我注意到这里有两个错误:

  1. abs在 p8 上的 select 语句中的使用。我只是省略abs了它,它起作用了。

  2. p27 上的空间搜索距离函数不会转换为弧度或将经度乘以cos(latitude),除非考虑到他的空间数据已加载(无法从文章的上下文中看出),但他在 p26 上的示例表明他的空间数据POINT未加载弧度或度数。

于 2015-09-04T15:18:29.430 回答
0
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515  as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";
于 2015-04-09T07:28:49.617 回答
0

使用 mysql

SET @orig_lon = 1.027125;
SET @dest_lon = 1.027125;

SET @orig_lat = 2.398441;
SET @dest_lat = 2.398441;

SET @kmormiles = 6371;-- for distance in miles set to : 3956

SELECT @kmormiles * ACOS(LEAST(COS(RADIANS(@orig_lat)) * 
 COS(RADIANS(@dest_lat)) * COS(RADIANS(@orig_lon - @dest_lon)) + 
 SIN(RADIANS(@orig_lat)) * SIN(RADIANS(@dest_lat)),1.0)) as distance;

见:https ://andrew.hedges.name/experiments/haversine/

请参阅:https ://stackoverflow.com/a/24372831/5155484

见:http ://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

注意:LEAST用于避免空值作为https://stackoverflow.com/a/24372831/5155484上建议的评论

于 2019-01-16T19:02:12.790 回答