我目前在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
。
答案 0 :(得分:111)
使用Point
表格中的Geometry
数据类型的MyISAM
值创建积分。 As of Mysql 5.7.5, InnoDB
tables now also support SPATIAL
indices.
在这些点上创建SPATIAL
索引
使用MBRContains()
查找值:
SELECT *
FROM table
WHERE MBRContains(LineFromText(CONCAT(
'('
, @lon + 10 / ( 111.1 / cos(RADIANS(@lon)))
, ' '
, @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 +/- 10 km, @lon +/- 10km)
框内的所有点。
这实际上不是一个盒子,而是一个球形矩形:球体的纬度和经度束缚段。这可能与 Franz Joseph Land 上的普通矩形不同,但在大多数有人居住的地方都非常接近。
应用其他过滤选择圈内的所有内容(不是方形)
可能会使用额外的精细过滤来计算大圆距离(适用于大距离)
答案 1 :(得分:96)
不是特定于MySql的答案,但它会提高sql语句的性能。
你实际做的是计算表格中每个点的距离,看它是否在给定点的10个单位内。
在运行这个sql之前你可以做的是创建四个点,在一边绘制一个20个单位的框,你的点在中心,即..(x1,y1)。 。 。 (x4,y4),其中(x1,y1)是(给定长+10个单位,给定为+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语句与我的结合起来了。
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我可以构建一个声明四个浮点数(X1,Y1,X2,Y2)并在“主”选择语句之前计算它们的SQL语句,就像我说的,我不知道这是否可以用MySql完成。但是我仍然倾向于在C#中构建四个点并将它们作为参数传递给SQL查询。
抱歉,如果有人能回答MySQL&amp; Php具体部分,请随时编辑此答案。
答案 2 :(得分:16)
查看此演示文稿以获得一个好的答案。 基本上它显示了评论中显示的两种不同的方法,详细解释了为什么/何时应该使用其中一种,以及为什么“在框中”计算可能非常有趣。
答案 3 :(得分:13)
在this blog post上,发布了以下MySql函数。我没有对它进行过多次测试,但根据我从帖子if your latitude and longitude fields are indexed收集的内容,这可能适合您:
DELIMITER $$
DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $$
CREATE FUNCTION get_distance_in_miles_between_geo_locations(geo1_latitude decimal(10,6), geo1_longitude decimal(10,6), geo2_latitude decimal(10,6), geo2_longitude decimal(10,6))
returns decimal(10,3) DETERMINISTIC
BEGIN
return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180) + COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180) * COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI()) * 60 * 1.1515);
END $$
DELIMITER ;
样本用法: 假设一个名为Places with fields latitude&amp;经度:
选择get_distance_in_miles_between_geo_locations(-34.017330, 22.809500,纬度,经度)作为distance_from_input from places;
答案 4 :(得分:9)
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) *
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) *
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)*
pi()/180))))*180/pi())*60*1.1515 ) as distance
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X
ORDER BY ID DESC
这是MySQL中点到点之间的距离计算查询,我在长数据库中使用它,它工作得很完美!注意:根据您的要求进行更改(数据库名称,表名称,列等)。
答案 5 :(得分:8)
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);
答案 6 :(得分:6)
如果您使用的是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
答案 7 :(得分:5)
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;
答案 8 :(得分:4)
完整代码包含有关如何安装为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;
}
答案 9 :(得分:3)
快速,简单和准确(对于较小距离)近似可以使用spherical projection来完成。至少在我的路由算法中,与正确的计算相比,我获得了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);
答案 10 :(得分:3)
以下是使用MySQL进行地理距离搜索的详细说明,该解决方案基于对mysql实施Haversine公式。完整的解决方案描述包括理论,实施和进一步的性能优化。虽然空间优化部分在我的情况下不正确。 http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
答案 11 :(得分:3)
阅读Geo Distance Search with MySQL,解决方案 基于Haversine Formula到MySQL的实现。这是一个完整的解决方案 理论描述,实施和进一步的性能优化。 虽然空间优化部分在我的情况下不能正常工作。
我发现了两个错误:
在p8的select语句中使用abs
。我刚刚省略了abs
,但它确实有效。
p27上的空间搜索距离函数不会转换为弧度或经度乘以cos(latitude)
,除非他的空间数据加载时考虑到这一点(无法从文章的上下文中得知),但他的例子在p26上表示他的空间数据POINT
未加载弧度或度数。
答案 12 :(得分:3)
一个MySQL函数,它返回两个坐标之间的米数:
CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000
要以不同的格式返回值,请将函数中的6371000
替换为您选择的单位中的地球半径。例如,公里为6371
,里程为3959
。
要使用该功能,只需像调用MySQL中的任何其他功能一样调用它。例如,如果你有一个表city
,你可以找到每个城市与其他城市之间的距离:
SELECT
`city1`.`name`,
`city2`.`name`,
ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
`city` AS `city1`
JOIN
`city` AS `city2`
答案 13 :(得分:3)
我需要解决类似的问题(按距单点距离的距离过滤行),并通过将原始问题与答案和注释相结合,我想出了对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
coordinates
是类型POINT
的字段,索引为SPATIAL
6371
用于计算以公里为单位的距离
56.946285
是中心点的纬度
24.105078
是中心点的经度
10
是以千米为单位的最大距离
在我的测试中,MySQL使用coordinates
字段上的SPATIAL索引来快速选择矩形内的所有行,然后计算所有已过滤位置的实际距离,以排除矩形角中的位置,而仅将圆内的位置保留。 / p>
这是我的结果的可视化结果:
灰色星星会显示地图上的所有点,黄色星星是MySQL查询返回的。 MBRContains()
选择矩形内角(但外圆)内的灰星,然后用HAVING
子句取消选择。
答案 14 :(得分:0)
使用mysql
SET @orig_lon = 1.027125;
SET @dest_lon = 1.027125;
SET @orig_lat = 2.398441;
SET @dest_lat = 2.398441;
SET @kmormiles = 6371;-- for distance in miles set to : 3956
SELECT @kmormiles * ACOS(LEAST(COS(RADIANS(@orig_lat)) *
COS(RADIANS(@dest_lat)) * COS(RADIANS(@orig_lon - @dest_lon)) +
SIN(RADIANS(@orig_lat)) * SIN(RADIANS(@dest_lat)),1.0)) as distance;
请参阅:https://andrew.hedges.name/experiments/haversine/
请参阅:https://stackoverflow.com/a/24372831/5155484
请参阅:http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
注意:LEAST
用于避免空值,这是对https://stackoverflow.com/a/24372831/5155484的建议
答案 15 :(得分:-1)
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515 as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";