我目前在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。


当前回答

有阅读地理距离搜索MySQL,一个解决方案 基于Haversine公式对MySQL的实现。这是一个完整的解决方案 描述与理论,实现和进一步的性能优化。 尽管空间优化部分在我的案例中没有正常工作。

我发现其中有两个错误:

在p8上的select语句中使用abs。我只是省略了腹肌,而且很有效。 p27上的空间搜索距离函数不转换为弧度或经度乘以cos(纬度),除非他的空间数据是考虑到这一点而加载的(不能从文章的上下文中判断),但他在p26上的例子表明他的空间数据POINT没有加载弧度或角度。

其他回答

我真的很喜欢@Māris kiseovs的解决方案,但我喜欢许多其他人可能会从他的例子中得到Lat和lng的POINTS。在概括它时,我想我会分享它。在我的情况下,我需要找到所有的开始点,在end_point的一定半径内。

我希望这能帮助到一些人。

SELECT @LAT := ST_X(end_point), @LNG := ST_Y(end_point) FROM routes  WHERE route_ID = 280;
SELECT 
  *,
  (6371e3 * ACOS(COS(RADIANS(@LAT)) * COS(RADIANS(ST_X(start_point))) 
  * COS(RADIANS(ST_Y(start_point)) - RADIANS(@LNG)) + SIN(RADIANS(@LAT))
  * SIN(RADIANS(ST_X(start_point))))) AS distance 
FROM routes
WHERE MBRContains
 (
  LineString
    (
    Point (
            @LNG + 15 / (111.320 * COS(RADIANS(@LAT))),
            @LAT + 15 / 111.133
    ),
    Point (
    @LNG - 15 / (111.320 * COS(RADIANS(@LAT))),
        @LAT - 15 / 111.133
    )
 ),
 POINT(ST_Y(end_point),ST_X(end_point))
)
HAVING distance < 100
ORDER By distance;

Create your points using Point values of Geometry data types in MyISAM table. As of Mysql 5.7.5, InnoDB tables now also support SPATIAL indices. Create a SPATIAL index on these points Use MBRContains() to find the values: 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 +/- 10km, @lon +/- 10km)。

这实际上不是一个盒子,而是一个球面矩形:纬度和经度绑定的球面段。这可能与弗朗茨约瑟夫土地上的普通矩形不同,但在大多数有人居住的地方都很接近。

应用额外的过滤来选择圆内的所有内容(不是正方形) 可能会应用额外的精细过滤来考虑大的圆距离(对于大的距离)

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

关于如何安装为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;
}

我需要解决类似的问题(从单点的距离过滤行),并通过结合原始问题的答案和评论,我想出了解决方案,这对我来说完美的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

坐标是POINT类型的字段,具有空间索引 6371是用千米来计算距离的 56.946285为中心点纬度 24.105078为中心点经度 15是最大距离,单位为千米

在我的测试中,MySQL使用SPATIAL index on coordinates字段来快速选择矩形内的所有行,然后为所有过滤过的地方计算实际距离,以排除矩形角落中的地方,只留下圆圈内的地方。

这是我的结果的可视化:

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