使用MySQL空间扩展来选择圆内的点

时间:2014-01-16 17:12:59

标签: mysql sql geospatial

我有一个名为flags的表,其中包含一个名为coordinates的列,其中包含MySQL'点'。我需要执行一个查询,我根据半径100米的纬度和经度位置得到一个圆圈内的所有标志。

从使用的角度来看,这是基于用户的位置。例如,移动电话将给出用户的纬度和经度位置,然后将其传递给API的这一部分。然后由API在半径为100米的用户周围创建一个不可见的圆圈,然后返回此圆圈中的标记。

API的这一部分我不知道如何创建,因为我不确定如何使用SQL创建此隐形圆并仅选择此半径内的点。

这可能吗?是否有 MySQL空间函数可以帮助我做到这一点?

我相信Buffer()函数可以做到这一点,但我找不到任何关于如何使用它的文档(例如示例SQL)。理想情况下,我需要一个答案,告诉我如何使用此函数或最接近它。在我将这些坐标存储为地理空间点的地方,我应该使用地理空间函数来做我要求最大化效率的问题。

旗帜表:

  • ID
  • 坐标
  • 名称

示例行:

1 | [几何 - 25B] | Tenacy AB

对于旗帜表,我有纬度,经度位置以及东向和北向(UTM)

用户的位置只是标准纬度/经度,但我有一个可以将此位置转换为UTM的库

8 个答案:

答案 0 :(得分:19)

MySQL中没有支持纬度/经度距离计算的地理空间扩展功能。 There is as of MySQL 5.7

你要求在地球表面上接近圆圈。您在问题中提到,flags表格中每行的纬度/经度值以及universal transverse Mercator(UTM)投影值均来自多个UTM zones之一。如果我记得我的英国军械测量局正确映射,UTM对于定位这些地图上的项目非常有用。

在UTM中计算同一区域中的两个点之间的距离是一件简单的事情:笛卡尔距离起作用。但是,当点位于不同的区域时,该计算不起作用。

因此,对于您问题中描述的应用,必须使用Great Circle Distance,这是使用半正弦或其他合适的公式计算的。

MySQL增加了地理空间扩展,支持一种将各种平面形状(点,折线,多边形等)表示为几何图元的方法。 MySQL 5.6实现了一个未记录的距离函数st_distance(p1, p2)。但是,此函数返回笛卡尔距离。因此,对于基于纬度和经度的计算,它完全不适合。在温带纬度地区,纬度对应的表面距离(南北)几乎是经度(东西向)的两倍,因为纬度线在靠近两极的地方越来越靠近。

因此,圆形接近公式需要使用真正的纬度和经度。

在您的应用程序中,您可以通过以下查询找到给定flags的十个法定里程内的所有latpoint,longpoint点:

 SELECT id, coordinates, name, r,
        units * DEGREES( ACOS(
                   COS(RADIANS(latpoint)) 
                 * COS(RADIANS(X(coordinates))) 
                 * COS(RADIANS(longpoint) - RADIANS(Y(coordinates))) 
                 + SIN(RADIANS(latpoint)) 
                 * SIN(RADIANS(X(coordinates))))) AS distance
   FROM flags
   JOIN (
        SELECT 42.81  AS latpoint,  -70.81 AS longpoint, 
               10.0 AS r, 69.0 AS units
        ) AS p ON (1=1)
  WHERE MbrContains(GeomFromText (
        CONCAT('LINESTRING(',
              latpoint-(r/units),' ',
              longpoint-(r /(units* COS(RADIANS(latpoint)))),
              ',', 
              latpoint+(r/units) ,' ',
              longpoint+(r /(units * COS(RADIANS(latpoint)))),
              ')')),  coordinates)

如果要搜索20公里范围内的点,请更改查询的这一行

               20.0 AS r, 69.0 AS units

,例如

               20.0 AS r, 111.045 AS units

r是您要搜索的半径。 units是地球表面上每个纬度的距离单位(英里,公里,弗隆,你想要的任何东西)。

此查询使用边界纬度/长度和MbrContains来排除距离起点太远的点,然后使用大圆距离公式生成剩余点的距离。一个explanation of all this can be found here。如果您的表使用MyISAM访问方法并且具有空间索引,MbrContains将利用该索引来快速搜索。

最后,上面的查询选择矩形内的所有点。要将其缩小到仅限于圆圈中的点,并按接近度排序,请将查询包装起来:

 SELECT id, coordinates, name
   FROM (
         /* the query above, paste it in here */
        ) AS d
  WHERE d.distance <= d.r
  ORDER BY d.distance ASC 

答案 1 :(得分:10)

这假设表中的坐标存储为标记为“point”的列中的POINT()数据类型。函数X(点)和Y(点)分别从点值中提取纬度和经度值。

SET @lat = the latitude of the point
SET @lon = the longitude of the point
SET @rad = radius in Kilometers to search from the point
SET @table = name of your table

SELECT
    X(point),Y(point),*, (
      6373 * acos (
      cos ( radians( @lat ) )
      * cos( radians( X(point) ) )
      * cos( radians( Y(point) ) - radians( @lon ) )
      + sin ( radians( @lat ) )
      * sin( radians( X(point) ) )
    )
) AS distance
FROM @table
HAVING distance < @rad

如果您想以英里为单位,请将常数6373替换为3959

对于那些想要减少查询语法的人来说,这是用户定义的MySQL函数的通用实现,用于实现基于Haversine公式的距离函数。

CREATE FUNCTION HAVERSINE ( coord1 POINT, coord2 POINT )
RETURNS DOUBLE
DETERMINISTIC
BEGIN
    DECLARE dist DOUBLE;
    SET rlat1 = radians( X( coord1 ) );
    SET rlat2 = radians( X( coord2 ) );
    SET rlon1 = radians( Y( coord1 ) );
    SET rlon2 = radians( Y( coord2 ) );
    SET dist  = ACOS( COS( rlat1 ) * COS( rlon1 ) * COS( rlat2 ) * COS( rlon2 ) + COS( rlat1 ) * SIN( rlon1 ) * COS( rlat2 ) * SIN( rlon2 ) + SIN( rlat1 ) * SIN( rlat2 ) ) * 6372.8;
    RETURN dist;
END

答案 2 :(得分:8)

<强>更新

使用ST_Distance_Sphere()使用纬度/经度计算距离

http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html#function_st-distance-sphere

答案 3 :(得分:5)

缓冲区对MySQL的帮助不大5.6,因为缓冲区是一个多边形,而MySQL中的多边形操作&lt; 5.6实现为“最小边界矩形”(MBR),这是非常无用的。

自MySQL 5.6以来,full non-MBR st_* operations were implemented。但对于您而言,最佳解决方案是使用undocumented函数st_distance

select *
from waypoints
where st_distance(point(@center_lon, @center_lat), coordinates) <= radius;

很难找到,因为它没有文档:-)但它在this blog上提到,其作者也填充了mentioned bugreport。但有一些警告(引用博客):

  

坏消息是:

     

1)所有功能仍然只使用平面系统坐标。   不支持不同的SRID。

     

2)仅MyISAM表支持空间索引(RTREE)。一   可以使用InnoDB表的函数,但它不会使用空间   密钥。

点1)表示距离单位与坐标单位(WGS84的情况下的度数)相同。如果您需要以米为单位的距离,则必须使用具有与米相对应的单位的预计协调系统(例如UTM或类似系统)。

所以,如果您不想使用这些警告,或者在MySQL&lt; 5.6,你必须编写自己的自定义距离函数。

答案 4 :(得分:1)

希望我的版本有所帮助

SELECT 
    *
FROM 
    `locator`
WHERE
    SQRT(POW(X(`center`) - 49.843317 , 2) + POW(Y(`center`) - 24.026642, 2)) * 100 < `radius`

详情请http://dexxtr.com/post/83498801191/how-to-determine-point-inside-circle-using-mysql

答案 5 :(得分:1)

自: https://gis.stackexchange.com/questions/31628/find-points-within-a-distance-using-mysql

SELECT
    id, (
      6371 * acos (
      cos ( radians(78.3232) )
      * cos( radians( lat ) )
      * cos( radians( lng ) - radians(65.3234) )
      + sin ( radians(78.3232) )
      * sin( radians( lat ) )
    )
) AS distance
FROM markers
HAVING distance < 30
ORDER BY distance
LIMIT 0 , 20;

(记得替换所有常量,这个例子是公里数)

答案 6 :(得分:0)

您可以使用:

SELECT name, lat, lng   
FROM vw_mytable 
WHERE ST_Contains(ST_Buffer(
          ST_GeomFromText('POINT(12.3456 34.5678)'), (0.00001*1000)) , mypoint) = 1  

上面的表达式:0.00001 * 1000,给你一个直径1000米的圆圈, 它被应用在这里的视图中,name列只是一个要指向的标签,mypoint是我的point列的名称, lat是使用ST_X(mytable.mypoint)和 与ST_Y(mytable.mypoint)一起使用,它们只显示lat和lng的文字值。 它会给你所有属于圆圈的坐标。

答案 7 :(得分:0)

为了完整起见,从MySQL 5.7.6开始。您可以使用ST_Distance_Sphere函数来获得相同的结果:

SET @pt1 = ST_GeomFromText('POINT(12.3456 34.5678)');

SELECT * from 
(SELECT * ,(ST_Distance_Sphere(@pt1, location, 6373)) AS distance FROM mydb.Event ORDER BY distance) x WHERE x.distance <= 30;

在这种情况下,我们以千米(6373)和一个点(@ pt1)提供地球的近似半径。此代码将计算该点(长12.3456,纬度34.5678)与距离为30km或更短的数据库中包含的所有点之间的距离(以千米为单位)。