从数据库中获取在标记半径范围内的结果。

20

2012年11月16日更新

我想再次提出这个问题,并为一个可靠、好的解决方案提供新的悬赏。似乎只有这个答案 (shubhansh's answer) 现在不能有效地工作。我将解释原因。

首先,这是我拥有的带有半径和人员的实时地图,半径用红色表示,人员用蓝色表示。

enter image description here

正如你所看到的,这张地图上有两个人和八个半径,基本上我只得到了其中的一个人Person A,但我没有得到Person B,我猜测 SQL 查询没有正确地获取它,我需要准确且精确地从人的半径和标记的半径中获取结果。

似乎被捕捉到的是在半径内的内容,而不是重叠半径的内容,我需要能够捕捉到任何重叠的半径的任何结果。

我正在寻找比 shubhansh 答案更精确和准确的 SQL。您可以阅读下面的内容,了解我需要查询如何操作并获取准确的人员信息。

数据,人员:

+-----------+-----------+--------+
| latitude  | longitude | radius |
+-----------+-----------+--------+
| 51.517395 | -0.053129 | 5.6    |
| 51.506607 | -0.116129 | 0.7    |
+-----------+-----------+--------+

请注意,radius的单位为公里。

+-----------+-----------+-----+
| latitude  | longitude | km  |
+-----------+-----------+-----+
| 51.502117 | -0.103340 | 0.3 |
| 51.498913 | -0.120850 | 0.7 |
| 51.496078 | -0.108919 | 0.7 |
| 51.496506 | -0.095873 | 0.7 |
| 51.503399 | -0.090723 | 0.7 |
| 51.508049 | -0.100336 | 0.7 |
| 51.508797 | -0.112610 | 0.7 |
| 51.505535 | -0.125227 | 0.7 |
| 51.502331 | -0.108061 | 0.7 |
+-----------+-----------+-----+

我当前使用的 SQL:

SELECT ppl.latitude,
       ppl.longitude,
       ppl.radius
FROM 
(
    people ppl
),
(
    SELECT latitude, longitude 
    FROM radiuses
) AS radius
WHERE (POW((ppl.longitude - radius.longitude) * 111.12 * COS(ppl.latitude), 2) + POW((ppl.longitude - radius.longitude) * 111.12, 2)) <= 4
GROUP BY ppl.id

这是您可以用来测试查询的MySQL数据,

INSERT INTO radiuses (id, latitude, longitude, km) VALUES ('1', '51.502117', '-0.103340', '0.3'), ('2', '51.498913', '-0.120850', '0.7'), ('3', '51.496078', '-0.108919', '0.7'), ('4', '51.496506', '-0.095873', '0.7'), ('5', '51.503399', '-0.090723', '0.7'), ('6', '51.508049', '-0.100336', '0.7'), ('7', '51.508797', '-0.112610', '0.7'), ('8', '51.505535', '-0.125227', '0.7'), ('9', '51.502331', '-0.108061', '0.7');

INSERT INTO people (id, latitude, longitude, radius) VALUES ('1', '51.517395', '-0.053129', '5.6'), ('2', '51.506607', '-0.116129', '0.7');

旧摘要

注意: 所有的纬度和经度都是随机生成的。

我有一个地图小程序,用户可以在其中放置他们所在位置的半径,半径为1km。

现在,还有另一个用户可以在地图上的任何位置放置他的半径,每个半径都为1km(与上面的用户相同)。

像这样,用户A是红色的,用户B是蓝色的。

enter image description here

基本上,用户A将其半径存储在一个表中,该表如下:

+-----------+---------+-----------+-----------+
| radius_id | user_id | latitude  | longitude |
+-----------+---------+-----------+-----------+
|         1 |       1 | 81.802117 | -1.110035 |
|         2 |       1 | 81.798272 | -1.144196 |
|         3 |       1 | 81.726782 | -1.135919 |
+-----------+---------+-----------+-----------+

用户B则将他的半径存储在另一个类似于这样的表中 -(注意:每个账户只能存储1个坐标):

+---------+-----------+-----------+
| user_id | latitude  | longitude |
+---------+-----------+-----------+
|       6 | 81.444126 | -1.244910 |
+---------+-----------+-----------+

我希望能够选择在定义的半径内的用户,即使半径圆相互接触在地图图片上。只有标记C才能捕捉到单个半径,而AB则不能。

我相信这是可能的,但我不知道如何在MySQL中设计此类系统。

我在Google开发者网站上找到了一个相关的资料,但并不完全满足我的需求。

编辑:我找到了一个更好的资料,虽然它使用了1个纬度和经度坐标边界,但仍未满足我的要求,因为我在表格中有多个坐标边界。


这可能是一个给你提示的答案http://stackoverflow.com/questions/11502469/find-records-with-lattitude-and-logintude/11502530#11502530,你只需要在查询中添加一个`WHERE distance < 1234`。 - fdomig
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
14

要解决这个问题,你需要理解圆的方程,它大致如下: 对于任意一个点 (x,y) 落在以圆心坐标为 (x1, y1),半径为 r 的圆内的条件是

(x-x1)^2 + (y - y1)^2 <= r^2

where a^b = a to the power b

在您的情况中,用户B的(纬度,经度)是圆心,用户A的(纬度,经度)是点(x,y),半径= 2公里。

但基本问题是将纬度的度数转换为经度的度数,所以这里是解决方案,1度=111.12公里。因此,为了保持等式两侧的单位相同,我们将其转换为公里。

因此,我们的最终方程为:

((x-x1)*111.12)^2 + ((y-y1)*111.12)^2 = 4      (=2^2) 

相同的SQL语句应该类似于这样

SELECT A.user_id, A.radius_id, A.latitude, A.logitude
FROM UserA AS A, 
     (SELECT user_id, latitude, longitude 
       FROM UserB 
       WHERE user_id = 8) AS B
WHERE (POW((A.latitude-B.latitude)*111.12, 2) + POW((A.longitude - B.longitude)*111.12, 2)) <= 4
/* **Edit** Here I have used (A.longitude - B.longitude)*111.12, for more accurate results one can replace it with (A.longitude - B.longitude)*111.12*cos(A.latitude)) or (A.longitude - B.longitude)*111.12*cos(B.latitude)) 

And, as i have suggested in the comments that first filter some records based on approximation, so whether one uses A.latitude or B.latitude it will not make much difference */

希望这能有所帮助...


2
一个纬度在赤道上等于111.12公里;随着你向极地移动,纬度保持相对恒定,但经度逐渐趋近于零,因此这个解决方案在离赤道越远的地方会变得越来越不准确。 - Ethan Brown
我认为上面给出的模型很好。如果需要更准确的距离结果,可以使用此处提供的公式 [http://www.movable-type.co.uk/scripts/latlong.html] 并在给定的查询中进行适当调整。 - jsist
为了加快查询速度,我建议您首先通过过滤经度和纬度+-2/112.12度来过滤解决方案,然后在结果公式上应用距离公式,以加快结果速度。 - jsist
@烧掉Codeigniter,加速的意思是,UserA中会有很多很多点,如果你要应用准确的距离公式,那肯定需要花费大量的时间。因此,解决方案是,首先只获取那些在UserB坐标附近范围内的点,然后对这些子集的值应用距离公式或普通公式(其中仅涉及POW())。这可以通过稍微修改查询来实现。查询写在下一个评论中(这里没有留足够的空间) - jsist
@shubhansh 我已经更新了我的帖子,以期获得更有效的解决方案。如果您能够提供的话。 - MacMac
显示剩余8条评论

7
你遇到的核心问题是如何判断两个圆是否重叠。答案是“如果它们的中心之间的距离小于它们半径的和,那么它们就重叠了”。所以你要找的是如何确定两点之间的距离。 另一个答案是将纬度和经度视为笛卡尔平面。但实际上它们不是(随着你从赤道向极地接近,经度趋近于零)。现在,作为近似值,这可能对你的解决方案完全有效,这取决于你的解决方案需要的精度。另一方面,如果你需要非常准确,你需要使用Haversine公式。此处有一个很好的MySQL实现描述:

http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

从那个演示文稿的第7张幻灯片中,你可以得到如下公式:

3956*2*ASIN(SQRT(POWER(SIN((orig.lat-dest.lat)*pi()/180/2),2)+
    COS(orig.lat*pi()/180)*COS(dest.lat*pi()/180)*
    POWER(SIN((orig.lon-dest.lon)*pi()/180/2),2)))
请注意,第一个数字是地球的平均半径,单位为英里;如果要用公里,请将其更改为6371。 如何使用这个计算出的距离取决于您帖子中未包含的细节,例如您要处理的点数、地理分布、任何性能要求以及数据是否静态或正在不断更新。 我提到这些事情是因为性能将是一个问题,特别是如果您有任何大量数据和/或它正在不断更新(例如基于用户手机GPS数据的位置)。 您可以通过使用正方形而不是圆形,并使用一个度数= 111.12公里的近似方法来帮助解决性能问题。这样,您就可以自动剔除显然相距太远的任何点。然后,您只需为那些落在感兴趣区域内的少数点计算Haversine公式。 希望这可以帮助指导您走向正确的方向。

6
您的几何学的关键点是,如果两个圆的中心之间的距离小于它们的半径之和,则它们重叠。由于我们正在进行比较,因此可以使用距离的平方,因为这样可以避免进行平方根运算。在原始问题中,每个半径固定为1,两个半径之和为2,和的平方为4。 原问题和新问题之间存在很大差异。在第一个问题中,您有固定半径的圆,而在第二个问题中,您有半径不同的圆。比较表达式中的常数4需要被替换,因为那是原始问题的固定半径的产物。为了实现这一点,请将km字段添加到查询中。并且应该检查,您没有在WHERE过滤器中使用ppl.radius,所以难怪改变该值并没有改变您的查询结果。
SELECT ppl.latitude, ppl.longitude, ppl.radius
FROM 
  ( people ppl ),
  ( SELECT latitude, longitude, km FROM radiuses ) AS B
WHERE [...distance^2...] <= POW( ppl.radius + B.km, 2)

我应该说,这个问题理解起来比预期的要花费更长的时间,因为您将不是人的实体称为“半径”,而实际上您有一个属性应该在两个不同的实体上被称为“半径”。所以给那个其他实体取一个描述性的名称。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,