查找两个纬度/经度点之间距离的最快方法
目前,我的 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
。
I currently have just under a million locations in a mysql database all with longitude and latitude information.
I am trying to find the distance between one point and many other points via a query. It's not as fast as I want it to be especially with 100+ hits a second.
Is there a faster query or possibly a faster system other than mysql for this? I'm using this query:
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;
Note: The provided distance is in Miles. If you need Kilometers, use 6371
instead of 3959
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
使用
MyISAM
表中Geometry
数据类型的Point
值创建点。 从 Mysql 5.7.5 开始,InnoDB
表现在也支持SPATIAL
索引。创建一个
SPATIAL
这些点的索引使用
MBRContains()
查找值:<前><代码>选择*
从表
WHERE MBRContains(LineFromText(CONCAT(
'('
, @lon + 10 / ( 111.1 / cos(弧度(@lat)))
, ''
,@lat + 10 / 111.1
, ','
, @lon - 10 / ( 111.1 / cos(弧度(@lat)))
, ''
,@lat - 10 / 111.1
, ')')
,我的观点)
、 或,在
MySQL 5.1
及更高版本中:这将选择大约在框
(@lat +/- 10 公里,@lon +/- 10km)
。这实际上不是一个盒子,而是一个球形矩形:球体的纬度和经度边界部分。 这可能与弗朗茨约瑟夫地上的普通矩形不同,但在大多数有人居住的地方非常接近。
应用额外的过滤来选择圆圈内的所有内容(不是正方形)
可能应用额外的精细过滤来考虑大圆距离(对于大距离)
Create your points using
Point
values ofGeometry
data types inMyISAM
table. As of Mysql 5.7.5,InnoDB
tables now also supportSPATIAL
indices.Create a
SPATIAL
index on these pointsUse
MBRContains()
to find the values:, or, in
MySQL 5.1
and above:This will select all points approximately within the box
(@lat +/- 10 km, @lon +/- 10km)
.This actually is not a box, but a spherical rectangle: latitude and longitude bound segment of the sphere. This may differ from a plain rectangle on the Franz Joseph Land, but quite close to it on most inhabited places.
Apply additional filtering to select everything inside the circle (not the square)
Possibly apply additional fine filtering to account for the big circle distance (for large distances)
不是 MySql 特定的答案,但它会提高 sql 语句的性能。
您实际上所做的是计算到表中每个点的距离,看看它是否在给定点的 10 个单位之内。
在运行此 SQL 之前,您可以做的是创建四个点,在一边绘制一个 20 个单位的盒子,您的点位于中心,即。 (x1,y1) 。 。 。 (x4, y4),其中 (x1,y1) 是 (givenlong + 10 单位,givenLat + 10 单位) 。 。 。 (给定长 - 10 个单位,给定纬度 -10 个单位)。
实际上,你只需要两个点,左上角和右下角分别称为(X1,Y1)和(X2,Y2)
现在你的SQL语句使用这些点来排除绝对超过10u的行您给定的点,它可以使用纬度和纬度上的索引 经度,因此将比您目前的速度快几个数量级。
例如,
盒子方法可能会返回误报(您可以在盒子的角上拾取距离给定点 > 10u 的点),因此您仍然需要计算每个点的距离。 然而,这又会快得多,因为您已经大大限制了要测试框内点的点数。
我将这种技术称为“框内思考”:)
编辑:可以将其放入一个 SQL 语句中吗?
抱歉,我不知道 mySql 或 Php 能做什么。
我不知道构建这四个点的最佳位置在哪里,也不知道如何将它们传递给 Php 中的 mySql 查询。 然而,一旦掌握了这四点,就没有什么可以阻止您将自己的 SQL 语句与我的结合起来。
我知道使用 MS SQL,我可以构建一个 SQL 语句,声明四个浮点数(X1、Y1、X2、Y2)并在“主”选择语句之前计算它们,就像我说的,我不知道这是否可以用MySql。 不过,我仍然倾向于在 C# 中构建这四个点并将它们作为参数传递给 SQL 查询。
抱歉,如果有人能回答 MySQL & 问题,我无法提供更多帮助。 PHP 的具体部分,请随意编辑此答案。
Not a MySql specific answer, but it'll improve the performance of your sql statement.
What you're effectively doing is calculating the distance to every point in the table, to see if it's within 10 units of a given point.
What you can do before you run this sql, is create four points that draw a box 20 units on a side, with your point in the center i.e.. (x1,y1 ) . . . (x4, y4), where (x1,y1) is (givenlong + 10 units, givenLat + 10units) . . . (givenLong - 10units, givenLat -10 units).
Actually, you only need two points, top left and bottom right call them (X1, Y1) and (X2, Y2)
Now your SQL statement use these points to exclude rows that definitely are more than 10u from your given point, it can use indexes on the latitudes & longitudes, so will be orders of magnitude faster than what you currently have.
e.g.
The box approach can return false positives (you can pick up points in the corners of the box that are > 10u from the given point), so you still need to calculate the distance of each point. However this again will be much faster because you have drastically limited the number of points to test to the points within the box.
I call this technique "Thinking inside the box" :)
EDIT: Can this be put into one SQL statement?
I have no idea what mySql or Php is capable of, sorry.
I don't know where the best place is to build the four points, or how they could be passed to a mySql query in Php. However, once you have the four points, there's nothing stopping you combining your own SQL statement with mine.
I know with MS SQL I can build a SQL statement that declares four floats (X1, Y1, X2, Y2) and calculates them before the "main" select statement, like I said, I've no idea if this can be done with MySql. However I'd still be inclined to build the four points in C# and pass them as parameters to the SQL query.
Sorry I can't be more help, if anyone can answer the MySQL & Php specific portions of this, feel free to edit this answer to do so.
我需要解决类似的问题(按距单点的距离过滤行),并通过将原始问题与答案和评论相结合,我想出了在 MySQL 5.6 和 5.7 上都非常适合我的解决方案。
坐标
是类型为POINT
的字段,并具有SPATIAL
索引6371
用于计算距离(以公里为单位)56.946285
是中心点的纬度24.105078
是中心点的经度15
是以公里为单位的最大距离在我的测试中,MySQL使用
坐标
字段上的SPATIAL索引来快速选择矩形内的所有行,然后计算所有过滤位置的实际距离排除矩形角的位置,只保留圆内的位置。这是我的结果的可视化:
灰色星星可视化地图上的所有点,黄色星星是 MySQL 查询返回的点。 矩形角内(但圆外)的灰色星星由
MBRContains()
选择,然后由HAVING
子句取消选择。I needed to solve similar problem (filtering rows by distance from single point) and by combining original question with answers and comments, I came up with solution which perfectly works for me on both MySQL 5.6 and 5.7.
coordinates
is field with typePOINT
and hasSPATIAL
index6371
is for calculating distance in kilometres56.946285
is latitude for central point24.105078
is longitude for central point15
is maximum distance in kilometersIn my tests, MySQL uses SPATIAL index on
coordinates
field to quickly select all rows which are within rectangle and then calculates actual distance for all filtered places to exclude places from rectangles corners and leave only places inside circle.This is visualisation of my result:
Gray stars visualise all points on map, yellow stars are ones returned by MySQL query. Gray stars inside corners of rectangle (but outside circle) were selected by
MBRContains()
and then deselected byHAVING
clause.以下 MySQL 函数发布于 这篇博文。 我没有对其进行太多测试,但是从我从帖子中收集到的信息来看,如果您的 纬度和经度字段已索引,这可能适合您:
示例用法:
假设一个名为
places
的表,其中包含字段latitude
&经度
:The following MySQL function was posted on this blog post. I haven't tested it much, but from what I gathered from the post, if your latitude and longitude fields are indexed, this may work well for you:
Sample usage:
Assuming a table called
places
with fieldslatitude
&longitude
:如果您使用 MySQL 5.7.*,则可以使用 st_distance_sphere(POINT, POINT)。
if you are using MySQL 5.7.*, then you can use st_distance_sphere(POINT, POINT).
这是MySQL中点之间的距离计算查询,我已经在一个很长的数据库中使用了它,它工作完美! 注意:根据您的要求进行更改(数据库名称、表名称、列等)。
This is the distance calculation query between to points in MySQL, I have used it in a long database, it it working perfect! Note: do the changes (database name, table name, column etc) as per your requirements.
来源
source
返回两个坐标之间的米数的 MySQL 函数:
要以不同格式返回值,请将函数中的
6371000
替换为您选择的单位中的地球半径。 例如,公里为6371
,英里为3959
。要使用该函数,只需像调用 MySQL 中的任何其他函数一样调用它即可。 例如,如果您有一个表
city
,您可以找到每个城市与其他城市之间的距离:A MySQL function which returns the number of metres between the two coordinates:
To return the value in a different format, replace the
6371000
in the function with the radius of Earth in your choice of unit. For example, kilometres would be6371
and miles would be3959
.To use the function, just call it as you would any other function in MySQL. For example, if you had a table
city
, you could find the distance between every city to every other city:有关如何安装为 MySQL 插件的详细信息的完整代码如下: https://github.com/lucasepe/lib_mysqludf_haversine< /a>
我去年发布了这个作为评论。 由于@TylerCollier 好心地建议我发布答案,所以就在这里。
另一种方法是编写一个自定义 UDF 函数,返回两点的半正矢距离。 这个函数可以接受输入:
所以我们可以这样写:
获取距离小于 40 公里的所有记录。 或者:
获取距离小于 25 英尺的所有记录。
核心功能是:
The full code with details about how to install as MySQL plugin are here: https://github.com/lucasepe/lib_mysqludf_haversine
I posted this last year as comment. Since kindly @TylerCollier suggested me to post as answer, here it is.
Another way is to write a custom UDF function that returns the haversine distance from two points. This function can take in input:
So we can write something like this:
to fetch all records with a distance less then 40 kilometers. Or:
to fetch all records with a distance less then 25 feet.
The core function is:
可以使用球形投影进行快速、简单且准确(对于较小距离)的近似。 至少在我的路由算法中,与正确的计算相比,我得到了 20% 的提升。 在 Java 代码中,它看起来像:
不确定 MySQL(抱歉!)。
确保您了解限制(assertEquals 的第三个参数表示以公里为单位的精度):
A fast, simple and accurate (for smaller distances) approximation can be done with a spherical projection. At least in my routing algorithm I get a 20% boost compared to the correct calculation. In Java code it looks like:
Not sure about MySQL (sorry!).
Be sure you know about the limitation (the third param of assertEquals means the accuracy in kilometers):
这是使用 MySQL 进行地理距离搜索的非常详细的描述,这是基于将半正矢公式实现到 mysql 的解决方案。 完整的解决方案描述,包括理论、实现和进一步的性能优化。 尽管空间优化部分在我的情况下无法正常工作。
http://www.scribd.com/doc/2569355/Geo -使用 MySQL 进行距离搜索
Here is a very detailed description of Geo Distance Search with MySQL a solution based on implementation of Haversine Formula to mysql. The complete solution description with theory, implementation and further performance optimization. Although the spatial optimization part didn't work correct in my case.
http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
阅读使用 MySQL 进行地理距离搜索,解决方案
基于半正矢公式到 MySQL 的实现。 这是一个完整的解决方案
描述理论、实现和进一步的性能优化。
尽管空间优化部分在我的情况下无法正常工作。
我注意到其中有两个错误:
在 p8 的 select 语句中使用
abs
。 我只是省略了abs
并且它起作用了。p27 上的空间搜索距离函数不会转换为弧度或将经度乘以
cos(latitude)
,除非他的空间数据加载时考虑到了这一点(无法从文章上下文中看出),但他在 p26 上的示例表明他的空间数据POINT
未加载弧度或度数。Have a read of Geo Distance Search with MySQL, a solution
based on implementation of Haversine Formula to MySQL. This is a complete solution
description with theory, implementation and further performance optimization.
Although the spatial optimization part didn't work correctly in my case.
I noticed two mistakes in this:
the use of
abs
in the select statement on p8. I just omittedabs
and it worked.the spatial search distance function on p27 does not convert to radians or multiply longitude by
cos(latitude)
, unless his spatial data is loaded with this in consideration (cannot tell from context of article), but his example on p26 indicates that his spatial dataPOINT
is not loaded with radians or degrees.使用 mysql
请参阅:https://andrew.hedges.name/experiments/haversine/
请参阅: https://stackoverflow.com/a/24372831/5155484
请参阅:http://www.plumislandmedia.net/mysql/haveversine-mysql-nearest-loc/
注意:< code>LEAST 用于避免空值,作为 https://stackoverflow.com/a/24372831/ 上建议的注释5155484
Using mysql
See: https://andrew.hedges.name/experiments/haversine/
See: https://stackoverflow.com/a/24372831/5155484
See: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
NOTE:
LEAST
is used to avoid null values as a comment suggested on https://stackoverflow.com/a/24372831/5155484我真的很喜欢@Māris Kiseļovs 解决方案,但我喜欢许多其他人可能将 Lat 和 lng 的
POINTS
与他的示例相反。 总的来说,我想我会分享它。 就我而言,我需要找到end_point
特定半径内的所有start_points
。我希望这可以帮助别人。
I really liked @Māris Kiseļovs solution, but I like many others may have the Lat and lng's
POINTS
reversed from his example. In generalising it I though I would share it. In my case I need to find all thestart_points
that are within a certain radius of anend_point
.I hope this helps someone.