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


当前回答

   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;

其他回答

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

你要做的是计算到表中每个点的距离,看看它是否在给定点的10个单位内。

在运行此sql之前,您可以做的是创建四个点,在一侧绘制一个20个单位的盒子,与您的点在中心,即。(x1,y1)。(x4, y4),其中(x1,y1)为(给定long + 10个单位,给定lat + 10个单位)…(给予龙-10单位,给予拉-10单位)。 实际上,你只需要两个点,左上右下分别是(X1, Y1)和(X2, Y2)

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

e.g.

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

方框方法可能会返回假阳性(您可以在方框的角落中拾取距离给定点> 10u的点),因此您仍然需要计算每个点的距离。然而,这同样会快得多,因为您已经极大地限制了测试框内的点的数量。

我把这个技巧叫做“在盒子里思考”:)

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

抱歉,我不知道mySql和Php能做什么。 我不知道构建这四个点的最佳位置,也不知道如何将它们传递给Php中的mySql查询。但是,一旦您掌握了这四点,就没有什么能阻止您将自己的SQL语句与我的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,我可以构建一个SQL语句,声明四个浮动(X1, Y1, X2, Y2),并在“主”选择语句之前计算它们,就像我说的,我不知道这是否可以用MySql完成。然而,我仍然倾向于用c#构建这四个点,并将它们作为参数传递给SQL查询。

对不起,我不能提供更多的帮助,如果有人可以回答MySQL和Php的具体部分,请随意编辑这个答案。

一个快速,简单和准确(对于较小的距离)的近似可以用球面投影完成。至少在我的路由算法中,与正确的计算相比,我得到了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);

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

   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;

如果你使用的是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