在一个位置周围生成随机坐标

24

我希望有一个函数,接受地理位置(纬度、经度),并围绕该位置生成随机坐标集合,但也将这些参数作为计算的一部分:

  • 要生成的随机坐标数量
  • 要生成的半径
  • 随机坐标之间的最小距离(以米为单位)
  • 要生成位置的根坐标。

以下是生成方式示例:

Example

实现这个功能的好方法是什么?


例如,您可以选择具有预定平均值或范围的正态分布半径和均匀分布角度。 - Kerrek SB
1
然后使用暴力方法来检查点之间的距离 :) - Karolis
是的,你可以选择一个随机角度和半径。但是,正确获取随机角度可能比较棘手。最好选择(在包括圆形的范围内)随机X和Y值,并且丢弃那些不在圆内的值(sqrt(X ** 2 + Y ** 2))。 - Hot Licks
这项任务的主要目的是什么?某种科学研究?还是可视化?最大点数是多少?您目前面临的主要问题是什么?速度? - Karolis
@Karolis Kinda,主要问题肯定是速度。 - CodeOverload
问题的棘手部分是最小距离。生成随机点将使找到适合的点变得越来越困难。也许,如果这足够随机,您可以首先将圆划分为圆内的正方形,并在随机可用的正方形中放置点? - Mathias
4个回答

12

在一个位置周围生成随机坐标

function generateRandomPoint($centre, $radius) {
    $radius_earth = 3959; //miles

    //Pick random distance within $distance;
    $distance = lcg_value()*$radius;

    //Convert degrees to radians.
    $centre_rads = array_map( 'deg2rad', $centre );

    //First suppose our point is the north pole.
    //Find a random point $distance miles away
    $lat_rads = (pi()/2) -  $distance/$radius_earth;
    $lng_rads = lcg_value()*2*pi();


    //($lat_rads,$lng_rads) is a point on the circle which is
    //$distance miles from the north pole. Convert to Cartesian
    $x1 = cos( $lat_rads ) * sin( $lng_rads );
    $y1 = cos( $lat_rads ) * cos( $lng_rads );
    $z1 = sin( $lat_rads );


    //Rotate that sphere so that the north pole is now at $centre.

    //Rotate in x axis by $rot = (pi()/2) - $centre_rads[0];
    $rot = (pi()/2) - $centre_rads[0];
    $x2 = $x1;
    $y2 = $y1 * cos( $rot ) + $z1 * sin( $rot );
    $z2 = -$y1 * sin( $rot ) + $z1 * cos( $rot );

    //Rotate in z axis by $rot = $centre_rads[1]
    $rot = $centre_rads[1];
    $x3 = $x2 * cos( $rot ) + $y2 * sin( $rot );
    $y3 = -$x2 * sin( $rot ) + $y2 * cos( $rot );
    $z3 = $z2;


    //Finally convert this point to polar co-ords
    $lng_rads = atan2( $x3, $y3 );
    $lat_rads = asin( $z3 );

    return array_map( 'rad2deg', array( $lat_rads, $lng_rads ) );
}

使用方法

generateRandomPoint(array(3.1528, 101.7038), 4);

9
一个简单的暴力方法应该足够了。
for each point to generate "n"
  find a random angle
  get the x and y from the angle * a random radius up to max radius
  for each point already generated "p"
     calculate the distance between "n" and "p"
  if "n" satisfies the min distance
     add new point "n"

在PHP中,生成新点很容易。
$angle = deg2rad(mt_rand(0, 359));
$pointRadius = mt_rand(0, $radius);
$point = array(
   'x' => sin($angle) * $pointRadius,
   'y' => cos($angle) * $pointRadius
);

计算两点之间的距离。
$distance = sqrt(pow($n['x'] - $p['x'], 2) + pow($n['y'] - $p['y'], 2));

** 编辑 **

为了澄清其他人所说的话,并进行了进一步的研究(我不是数学家,但评论确实让我想),这里提供一个最简单的高斯分布定义:

如果您在一维空间中,则 $pointRadius = $x * mt_rand(0, $radius); 可以正常使用,因为当 $x 具有高斯分布时,$radius 和 $x之间没有区别。

然而,在二维或更高维空间中,如果坐标($x,$y,...)具有高斯分布,则半径 $radius 不具有高斯分布。

实际上,在2维[或k维]中,$radius^2 的分布被称为“具有2 [或k]自由度的卡方分布”,前提是($x,$y,...)是独立的,并且具有零均值和相等方差。

因此,要有正态分布,您需要将生成的半径行更改为

$pointRadius = sqrt(mt_rand(0, $radius*$radius));

如其他人所建议的那样。

2
使用这种方法,您将获得一个拥有360个光线的太阳 :) - Karolis
问题在于它的运行时间为O(n^2),而它很容易达到O(n log n) - Daniel
@Karolis,如果半径不太大,那么就可以。问题没有具体说明。在任何情况下,可以通过在0359000之间取一个随机值,并将结果除以1000来获得千分精度的值,等等,从而轻松改善精度。是的,半径也应该是随机的 :) - Yanick Rochon
@YanickRochon 你不能使用不同的半径来描述 x 和 y。 - Karolis
这些点将集中在中心周围 - 随着向外移动,密度会减少。我真的不认为这是你想要的。 - andrew cooke

3
如其他答案所述,最简单的方法是生成随机点,然后丢弃那些离其他点太近的点(如果需要,不要忘记检查到中心点的最小距离)。但是,生成随机点比解释的要困难。首先,您需要随机选择半径。其次,您需要在大半径处具有更多的点(因为那里“更宽敞”)。因此,您不能仅仅将半径作为均匀随机数。相反,选择0到$radius * $radius之间的数字。然后使用sqrt()找到要绘制的半径(这是因为面积与半径的平方成比例)。我不知道php(请参见Karolis在评论中的更正),但从其他答案中我认为应该这样做:
$angle = deg2rad(mt_rand(0, 359));
$radius = sqrt(mt_rand(0, $max_radius * $max_radius));

其次,按照之前的描述检查与先前点的距离。

最后,不要忘记可以达到无法生成更多点的状态,因此您可能需要在“尝试和丢弃”循环中设置上限,以避免在空间(接近)充满时进入无限循环。

附注:正如另一篇答案上所说,这是O(n^2),因此不适用于大量的点。您可以通过按半径对点进行排序并仅考虑那些半径差小于 $min_distance 的点来解决这个问题,只要 $min_distance << $max_radius(如您的绘图所示);做得比这更好需要使用更复杂的解决方案(例如,在较大半径处还使用角度,或者使用单独的四叉树存储和比较位置)。但是对于数十个点,我想这是不必要的。


1
注意:mt_rand()函数返回的是一个整数,这不是我们需要的。我们可以使用以下方法获取随机角度:$angle = lcg_value() * 2 * pi()。并且使用以下方法获取随机半径:$radius = sqrt(lcg_value() * $max_radius * $max_radius) - Karolis
为什么不在一个适合圆形的正方形内生成随机点(这很容易),然后丢弃不落在圆内的点呢? - Peter Ajtai
你可以这样做;我怀疑上述方法略微更有效。 - andrew cooke

1

其他人已经解释了你需要的数学知识。但我认为最棘手的部分是性能。当你只有50个点时,检查点之间距离的暴力方法可能足够好。但是当你有1000个或更多点时,速度就会变得太慢了。对于1000个点,这至少需要500,000次操作。

因此,我的建议是将所有随机生成的点保存到B树二叉搜索树(按x值和y值排序)。使用有序树,您将能够高效地获取在区域[x ± min_distance,y ± min_distance]内的点。而这些是唯一需要检查的点,大大减少了所需操作的数量。


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