查询给定经纬度范围内的地点

6

我想展示一些在给定经纬度范围内的地点列表,这些地点与所给的经纬度值相距不远。我对此没有任何问题:

距离为一英里以内的地点 (地点清单...)

可以使用类似以下的代码:

SELECT * FROM places WHERE lat < $latmax AND lat > $latmin AND lng < $lngmax AND lng > $lngmin

但是我想列出两英里内的地点,但不包括一英里以内的地点——也就是说,我不想重复第一个查询的结果。

以下是我尝试过的一种版本:

$milesperdegree = 0.868976242 / 60.0 * 1.2;

// 1 mile -- this works
$degrees = $milesperdegree * 1;
$latmin = $lat - $degrees;
$latmax = $lat + $degrees;
$lngmin = $lng - $degrees;
$lngmax = $lng + $degrees;

$query = "SELECT * FROM places WHERE lat < $latmax AND lat > $latmin AND lng < $lngmax AND lng > $lngmin";

// 2 miles -- this doesn't work
$degrees_2 = $milesperdegree * 2;
$latmin_2 = $lat - $degrees_2;
$latmax_2 = $lat + $degrees_2;
$lngmin_2 = $lat - $degrees_2;
$lngmax_2 = $lat + $degrees_2;

$query = "SELECT * FROM places WHERE ";
$query .= "lat BETWEEN $latmax AND $latmax_2 AND lng BETWEEN $lngmax AND $lngmax_2 OR ";
$query .= "lat BETWEEN $latmin AND $latmin_2 AND lng BETWEEN $lngmin AND $lngmin_2 OR ";
$query .= "lat BETWEEN $latmax AND $latmax_2 AND lng BETWEEN $lngmin AND $lngmin_2 OR ";
$query .= "lat BETWEEN $latmin AND $latmin_2 AND lng BETWEEN $lngmax AND $lngmax_2";

这样做不行。我猜测这只是一些逻辑问题,可能是因为周日下午我无法理解,但我也可能做错了其他事情。非常感谢您的任何建议。


你意识到这个脚本只是在点周围画了一个框(而不是圆)吗?另外,$milesperdegree是纬度的一个函数(从数学上讲)。有一个很好的脚本可以用 - 虽然是用JavaScript编写的 - 链接 - Czechnology
我没有意识到圆形/正方形的部分。我猜经度变量应该是$lng +/- x英里,而不是$milesperdegree? - nickfindley
至于圆形/正方形部分 - 应用勾股定理即可解决。 - Czechnology
@nickfindley,我已经添加了一个“圆形”解决方案,请查看我的答案。 - Czechnology
@Czechnology 实际上,度数之间的距离与地球不是一个完美的球体无关(虽然这确实有微小的影响,但相当可忽略)。这更多是与距离中心的距离有关。圆的中心是南北极。任何 x 度的弧长随着距离中心的增加而增加。通过查看量角器的图表应该很清楚:http://www.farlaboratories.com/dyna-a-prot.html - Endophage
显示剩余3条评论
3个回答

4
我们实现的大致代码如下(声明:我从一个文件中剪切了这段代码,并删除了与手头问题无关的代码。我没有运行过它,但你应该能够理解它的思路。)
$maxLat = $city->latitude + ($max_distance / 69); // 69 Miles/Degree
$minLat = $city->latitude - ($max_distance / 69);

$maxLon = $city->longitude + ($max_distance / (69.172 * cos($city->latitude * 0.0174533)));
$minLon = $city->longitude - ($max_distance / (69.172 * cos($city->latitude * 0.0174533)));

// Simplify terms to speed query
$originLatRadCos = cos($city->latitude * 0.0174533);
$originLatRadSin = sin($city->latitude * 0.0174533);
$originLonRad = $city->longitude * 0.0174533;

$city_distance_query = "
SELECT city_id, 
  3963 * acos(($originLatRadSin * sin( latitude * 0.0174533)) + 
  ($originLatRadCos * cos(latitude * 0.0174533) * cos((longitude * 0.0174533) -
  $originLonRad))) AS distanceFromOrigin
FROM cities
WHERE
 latitude < $maxLat AND latitude > $minLat AND longitude < $maxLon AND longitude > $minLon";

查询的其余部分
SELECT cities.city_name, CityDistance.distanceFromOrigin,
FROM cities 
INNER JOIN ($city_distance_query) AS CityDistance ON CityDistance.city_id=cities.city_id
WHERE (distanceFromOrigin < $distance OR distanceFromOrigin IS NULL) 

当然不会猜到这个。谢谢大家。 - nickfindley
非常感谢您的帖子。让我省了很多事!非常容易适应和清晰明了。 - Paul T. Rawkeen

2

我认为您错过了一些括号,并且逻辑运算符有点混淆了。尝试这个。

$query  = "SELECT * FROM places WHERE ";
$query .= "((lat BETWEEN $latmin_2 AND $latmax_2) AND NOT (lat BETWEEN $latmin AND $latmax)) AND ";
$query .= "((lng BETWEEN $lngmin_2 AND $lngmax_2) AND NOT (lng BETWEEN $lngmin AND $lngmax)) AND ";

编辑

解决圆/正方形问题的方法:

$query  = "SELECT * FROM places WHERE ";
$query .= "(POW((lat - $lat) * $avgMilesPerLatDeg,2) + ".
           "POW((lng - $lng) * $avgMilesPerLngDeg,2) BETWEEN 1 AND 4)";
// the four at the end is 2 squared

如果你的应用程序不是大规模的(从地理角度来看),并且平均值能够产生可接受的结果,我建议使用这种方法。计算“真实”距离需要更长时间来计算,而差异可能并不那么大。这取决于你和应用程序的目标。


快要成功了!但我必须承认,至少在数学上,我不太明白正在发生什么。那个查询是什么,可以获取两英里到三英里之间的地点? - nickfindley
这个应用程序中,我只会走三英里远的地方——大致在附近就足够了。虽然我感谢为将来提供的知识。 - nickfindley
@nickfindley,这实际上就是勾股定理 a^2 + b^2 = c^2,在我们的情况下是 n <= (x-a)^2 + (y-b)^2 <= m。因此,对于两到三英里之间的地方,您需要使用 ... BETWEEN POW(2,2) AND POW(3,2),这相当于 ... BETWEEN 4 AND 9 - Czechnology

0

这是我认为你需要的:使用lat/lng在MySQL中计算距离。这将给你一个圆形,并且有你需要的排除能力。

按照帖子中描述的步骤进行操作,但不要

HAVING `distance`<= 10

您需要放置。
HAVING `distance` BETWEEN 1 AND 2

这将会给你在范围内的东西。

附注:如果你有一个包含大量记录的数据库 - 你需要对其进行基准测试以了解其性能,需要进行一些优化(如果性能不可接受)。


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