如何在具有指定半径的圆内生成随机坐标?

Den*_* S. 2 ruby

我正在尝试生成位于半径为5公里的圆内的随机坐标(纬度,长度),其中心点位于某个坐标(x,y)处.我试图用红宝石编码,我正在使用该方法但不知何故,我得到的结果不在指定的5公里范围内.

def location(lat, lng, max_dist_meters)
  max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)
  lat_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
  lng_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
  lat += [1,-1].sample * lat_offset
  lng += [1,-1].sample * lng_offset
  lat = [[-90, lat].max, 90].min
  lng = [[-180, lng].max, 180].min
  [lat, lng]
end
Run Code Online (Sandbox Code Playgroud)

Eri*_*nil 11

你的代码

max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)
Run Code Online (Sandbox Code Playgroud)

这只是max_dist_meters.abs / Math.sqrt(2)max_dist_meters * 0.7071067811865475.

10 ** (Math.log10(max_radius / 1.11)-5)
Run Code Online (Sandbox Code Playgroud)

这可以写9.00901E-6 * max_radius,所以它6.370325E?6 * max_dist_meters.

rand(10 ** (Math.log10(max_radius / 1.11)-5))
Run Code Online (Sandbox Code Playgroud)

现在为有趣的部分:rand(x)就是rand()如果x介于-1和之间1.所以,如果max_dist_meters小于1/6.370325E?6 ~ 156977.86,你的所有3个第一行都是:

lat_offset = rand()
lng_offset = rand()
Run Code Online (Sandbox Code Playgroud)

因此,对于max_dist_meters = 5000,您的方法将返回一个随机点,该点可能是1°经度和1°纬度.最多,它将超过157公里.

更糟糕的是,如果x156978和之间313955,您的代码相当于:

lat_offset = lng_offset = 0
Run Code Online (Sandbox Code Playgroud)

自Ruby 2.4起

[[-90, lat].max, 90].min
Run Code Online (Sandbox Code Playgroud)

可以写 lat.clamp(-90, 90)

可能解决方案

要在半径磁盘上均匀分布随机点max_radius,您需要随机半径的非均匀分布:

def random_point_in_disk(max_radius)
  r = max_radius * rand ** 0.5
  theta = rand * 2 * Math::PI
  [r * Math.cos(theta), r * Math.sin(theta)]
end
Run Code Online (Sandbox Code Playgroud)

这是一个有百万随机点的情节:

统一分配

这是与@ Schwern代码相同的情节:

分布不均匀

使用此方法后,您可以应用一些基本数学将米转换为纬度和经度.请记住,1°的纬度始终为111.2km,但1°的经度在赤道为111.2km,而在极地为0km:

def random_point_in_disk(max_radius)
  r = max_radius * rand**0.5
  theta = rand * 2 * Math::PI
  [r * Math.cos(theta), r * Math.sin(theta)]
end

EarthRadius = 6371 # km
OneDegree = EarthRadius * 2 * Math::PI / 360 * 1000 # 1° latitude in meters

def random_location(lon, lat, max_radius)
  dx, dy = random_point_in_disk(max_radius)
  random_lat = lat + dy / OneDegree
  random_lon = lon + dx / ( OneDegree * Math::cos(lat * Math::PI / 180) )
  [random_lon, random_lat]
end
Run Code Online (Sandbox Code Playgroud)

对于这种计算,不需要安装800磅重的GIS大猩猩.

几点:

  • 我们通常谈论纬度优先和经度第二,但在GIS中,它通常lon首先是因为x之前y.
  • cos(lat)被认为是不变的所以max_radius不应该太大.几十公里不应该造成任何问题.球体上的圆盘形状变得很奇怪,半径很大.
  • 不要使用这种方法太靠近极点,否则你会得到任意大的坐标.

为了测试它,让我们在不同位置的200km磁盘上创建随机点:

10_000.times do
  [-120, -60, 0, 60, 120].each do |lon|
    [-85, -45, 0, 45, 85].each do |lat|
      puts random_location(lon, lat, 200_000).join(' ')
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

使用gnuplot,这是结果图:

在此输入图像描述

好极了!我刚刚重新发明了天梭的指纹,已经晚了150年:

在此输入图像描述

  • 我喜欢你解决转换回lat/long,很好的工作! (4认同)