问题
我当前使用 MySQL POINT
字段类型存储某个位置的纬度
和 经度
值,格式如下:
POINT(51.507351 -0.127758)
我以前从未使用过这种字段类型,因此对查询以及如何实际、高效地使用存储的数据没有任何经验。
我的研究
我发现许多链接演示了在指定半径内搜索项目的各种方法。但是,其中大多数都使用独立的纬度
和经度
字段,而不是使用MySQL空间字段。
请参阅以下内容:
- Fastest Way to Find Distance Between Two Lat/Long Points
- http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
- Use MySQL spatial extensions to select points inside circle
我的问题
我正在尝试搜索给定半径(以米为单位)内的任何记录。根据我的表的结构,搜索记录并返回指定半径(圆形而不是矩形)内的任何项目的最佳且最有效的方法是什么?
这是我到目前为止所拥有的:
SELECT
*,
(
6373 * acos (
cos ( radians( PASSED_IN_LATITUDE ) )
* cos( radians( X(location) ) )
* cos( radians( Y(location) ) - radians( PASSED_IN_LONGITUDE ) )
+ sin ( radians( PASSED_IN_LATITUDE ) )
* sin( radians( X(location) )
)
) AS distance
FROM locations
HAVING distance < PASSED_IN_RADIUS
我从另一个答案中获取了上述代码,但考虑到这个答案是在两年前发布的,我认为它已经过时了,因此这可能不再是最有效的方法...
最佳答案
假设您在位置上有一个空间键,您可以执行以下操作:
select * from locations where
contains(geomfromtext('polygon($bounding_rect_coords)'),location)
and earth_distance(location,point($lat,$lon)) < $radius
边界矩形坐标应使用以下公式计算:
$deg_to_rad = $PI/180.0
$rad_to_deg = 1.0/$deg_to_rad
$delta_y = $rad_to_deg *($radius / ($earth_radius * cos($lat*$deg_to_rad))) // the length of the parallel = EARTH_R * cos(lat)
$delta_x = $rad_to_deg * ($radius/$earth_radius)
$x1 = $lat - $delta_x
$x2 = $lat + $delta_x
$y1 = $lon - $delta_y
$y2 = $lon + $delta_y
然后得到矩形
geomfromtext('polygon(($x1 $y1,$x2 $y1,$x2 $y2, $x1 $y2, $x1 $y1))')
最好在应用程序中完成此操作,以减轻数据库服务器的负担。
这个矩形实际上是一个球面矩形,因此在其计算中使用了 PI 常数。这个想法很简单。对于给定的纬线,将搜索半径转换为经度。这就是我们需要从目标向东和向西移动多少度才能覆盖我们的候选点。然后计算相同的纬度 - 与经度不同,这不会依赖于坐标,因为所有经线都具有相同的长度。这就是我们需要向北和向南移动多少度。
上述计算假设搜索半径小于平行线的长度,这在美国大部分地区都是合理的搜索半径,但在阿拉斯加的某些地区可能不成立。因此,最好检查一下(如果 delta_y > 90)并相应地对其进行剪辑。您还应该检查一下您是否位于北极或南极,那里的情况完全破裂。但希望您的数据没有太多极地记录。
对于earth_distance()
,您有多种选择:
- 使用我的 UDF ( http://github.com/spachev/mysql_udf_bundle )(速度最快,但您需要能够在服务器上安装 UDF)
- 编写一个 MySQL 存储函数。您可以从 http://gist.github.com/aramonc/6259563 开始并根据需要进行调整(需要创建函数的能力)。
- 只需将上面的距离计算直接粘贴到查询中(丑陋,但不需要特殊设置或权限)
尽管你的计算本身已经有两年了,但它本身还是可以的 - 据我所知,在过去两年中,就测量地球上两点之间的距离而言,没有发现任何革命性的发现。
你原来的方法也可以工作,只是效率低下。添加 contains
子句使我们能够将搜索减少到(希望)相对较小的集合,并保证很快就在搜索半径内。然后我们选取每个候选者并过滤掉那些没有在 earth_distance()
上晋级的候选者。
我必须添加一个标准免责声明,即我在 SQL 中插入了可能尚未清理的变量。在编写实际的生产代码时,请确保验证生成的 SQL 查询是否存在 SQL 注入(inject)攻击。
关于php - 使用 MySQL 空间字段查找地理围栏(圆圈)内的记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37444945/