查询距离

时间:2011-12-08 17:19:18

标签: php mysql sql geospatial spatial

不确定如何建立这样的开放给专家:

我在db中有一个客户列表,它们正在寻找位置半径范围内的事件。我可以存储他们的邮政编码(或lat / lng)以及他们将参加活动的最大距离。所以列lat,lng,distance(例如:lat = '22 .7447858',lng =' - 82.1398589',距离= 25)。

活动全天发布,并存储了zipcode / lat / lng。

我想运行一个查询(每天一次),以获取活动的客户。我正在查看Cyber​​Junkies帖子Mysql within distance query,但问题是我正在以相反的方向运行查询。我需要找到“圆距离”涵盖当前事件的客户,而不是其他方式。不确定如何存储圆距离(上面的3列是否足够好,或者是否有更好的方法来存储此类查询的数据)?不确定如何按事件查询客户。

提前致谢!

5 个答案:

答案 0 :(得分:2)

我认为有两种主要方法:动态计算距离,预先计算距离,然后将它们存储在查找表中。

选项1 ,即时计算。 Tom van der Woerdt的回答很好地解释了你将如何做到这一点。伪代码查询是这样的:

SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance

选项2 ,预先计算所有距离。您将创建一个表(在此示例中称为distance),用于存储每个客户与每个事件之间的距离。它有三列:customerideventidmiles(或您想要的任何距离指标)。遍历每个计算每个事件距离的客户,并将每个事件存储在distance中。每次添加新客户或事件时,都会将相应的记录添加到distance表中。一旦这个结构到位,找到事件就会很简单:

SELECT * FROM distance WHERE miles < [[some number you pick]]

哪一个更好?这是CPU时间和磁盘空间之间的权衡,所以答案取决于您的资源。选项1(动态计算)将需要DBMS进行更多工作(更多CPU时间)。随着人员和事件数量的增加,该查询将需要更长时间才能运行。选项2(预先计算距离)将使查找非常快,但权衡是您必须将所有这些预先计算的距离存储在磁盘上。您还需要努力确保您的查找表是最新的。每次添加,删除客户或事件或更改其纬度/经度时,您都需要相应地更新查找表。 Triggers可以帮助您自动完成此过程;只是确保你尝试测试每个场景(添加,删除,移动),以确保查找表像预期的那样得到更新。

简答:如果您的数据库负载非常小和/或磁盘空间有限,请选择选项1(即时计算)。如果您负载很重但是磁盘空间很大,请选择选项2。选项2更可能是场景,而且可扩展性更高。

答案 1 :(得分:0)

你想要的是:

SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance

这将简单地获取所有客户和所有事件,将它们组合在一起以获得所有可能的组合(100个客户和10个事件提供1000个组合),然后检查它们是否在范围内。 *

我个人建议制作一个DISTANCE(customer,event)函数来为您计算。以这种方式管理查询更容易,您可以重复使用它。

* 不一定按照该顺序

答案 2 :(得分:0)

从A点到B点的距离将与从B点到A点的距离相同(除非您正在处理道路方向和不同路径)。

基本上你要做(在sql伪代码中)

SELECT distance(event_loc, user_loc) <= user_max_distance

答案 3 :(得分:0)

如果您的距离计算与this solution中的距离计算类似,那么您可以执行以下操作:

select id1 from Distances 
    join EventTable on id2=EventTable.eventid 
    join UserTable on id1=UserTable.userid 
where type2=<EVENT_TYPE> and type1=<USER_TYPE> 
    and geodistance_km_by_obj(id1,<USER_TYPE>,id2,<EVENT_TYPE>) < UserTable.max_distance

答案 4 :(得分:0)

除了其他答案之外,您可能会考虑使用笛卡尔坐标(x,y和z)而不是lat / lng来进行数据库存储,因为生成的查询表达式在数据库服务器上的加载/时间比可能更简单查询lat / lng距离。

可以在以下位置找到PHP实现的示例:

http://headers-already-sent.com/geodistance/

方法“getCartesian”将lat / lng转换为笛卡尔坐标,方法“getDistanceByCartesian”显示如何计算实际距离。你需要做的是将这个距离计算从PHP转移到SQL查询(不应该那么复杂)。

编辑,因为我找到时间来提供更实际的例子

根据您在上述链接中可以找到的课程,我为我的公司位置和附近的所有MC Donalds餐厅设置了2个演示表,并将谷歌地图中的lat / lng转换为笛卡尔x,y,z:< / p>

CREATE TABLE `locations` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL DEFAULT '',
  `lat` double NOT NULL,
  `lng` double NOT NULL,
  `x` double NOT NULL,
  `y` double NOT NULL,
  `z` double NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `locations` (`id`, `title`, `lat`, `lng`, `x`, `y`, `z`)
VALUES
    (1,'Ida-Ehre-Platz 10, 20095 Hamburg',53.55053,9.99949,3727600.05477,657242.251356,5124712.81705),
    (2,'Kieler Straße 191-193, 22525 Hamburg',53.57731,9.93686,3725956.4981,652753.812254,5126481.40905),
    (3,'Reeperbahn 42, 20359 Hamburg',53.549951,9.964937,3728046.74189,655003.113578,5124674.56664),
    (4,'Theodor-Heuss-Platz 3, 20354 Hamburg',53.56083,9.99038,3726797.15378,656489.722425,5125393.17725),
    (5,'Mundsburger Damm 67, 22087 Hamburg',53.57028,10.02642,3725550.98379,658686.623655,5126017.24553),
    (6,'Paul-Nevermann-Platz 1, 22765 Hamburg',53.552602,9.936678,3728135.78521,653123.397726,5124849.69505),
    (7,'Friedrich-Ebert-Damm 101, 22047 Hamburg',53.58753,10.08958,3723303.02881,662522.688778,5127156.05819),
    (8,'Amsinckstraße 73, 20097 Hamburg',53.54271,10.02654,3727978.07563,659123.791421,5124196.16112),
    (9,'Eiffestraße 440, 20537 Hamburg',53.55214,10.04638,3726919.13256,660267.521487,5124819.17553);


CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '',
  `lat` double NOT NULL,
  `lng` double NOT NULL,
  `x` double NOT NULL,
  `y` double NOT NULL,
  `z` double NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`, `name`, `lat`, `lng`, `x`, `y`, `z`)
VALUES
    (1,'Ministry.BBS, Cremon 36, 20457 Hamburg',53.545943,9.988761,3728127.10678,656615.385203,5124409.77226),
    (2,'BBS, Dorotheenstraße 60, 22301 Hamburg',53.583231,10.008315,3724617.80169,657307.963226,5126872.28974);

基于这两个表,SQL-Query可以找到每个用户(我们公司的办事处)内一定距离(2000,以米为单位)内的所有位置(餐馆):

SELECT locations.*,
    2 * 6371000.785 *
        asin(
            sqrt(
                pow(locations.x - user.x, 2)
                + pow(locations.y - user.y, 2)
                + pow(locations.z - user.z, 2)
            ) / (2 * 6371000.785)
        ) AS distance
    FROM locations, user
    HAVING distance < 2000 
    ORDER BY distance ASC

如果你需要的不是“米”,那么你必须改变大约的地球半径。 6371000.785(以米为单位),无论您需要什么,还可以将所需距离2000更改为您喜欢的任何距离或存储在用户表中的每个用户。