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

时间:2017-04-03 23:23:46

标签: 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

4 个答案:

答案 0 :(得分:11)

您的代码

max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)

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

10 ** (Math.log10(max_radius / 1.11)-5)

这可以写成9.00901E-6 * max_radius,因此它是6.370325E−6 * max_dist_meters

rand(10 ** (Math.log10(max_radius / 1.11)-5))

现在有趣的部分:rand(x)只有rand(),如果x介于-11之间。因此,如果max_dist_meters小于1/6.370325E−6 ~ 156977.86,则您的所有3个第一行都是:

lat_offset = rand()
lng_offset = rand()

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

更糟糕的是,如果x介于156978313955之间,则您的代码相当于:

lat_offset = lng_offset = 0

自Ruby 2.4起

[[-90, lat].max, 90].min

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

可能的解决方案

要在半径为max_radius的磁盘上均匀分布随机点,您需要a non-uniform distribution of random radii

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

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

Uniform distribution

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

Non-uniform distribution

使用此方法后,您可以应用一些基本数学来将米转换为纬度和经度。请记住,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

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

几点:

  • 我们通常会谈论纬度优先和经度第二,但在GIS中,它通常是lon,因为x位于y之前。
  • cos(lat)被认为是不变的,因此max_radius不应该太大。几十公里不应该造成任何问题。球体上的磁盘形状变为weird with a large radius
  • 不要太靠近两极使用这种方法,否则你会得到任意大的坐标。

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

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

使用gnuplot,这是结果图:

enter image description here

耶!我刚刚彻底改造了Tissot's indicatrix,已经晚了150年:

enter image description here

答案 1 :(得分:3)

def location(x_origin, y_origin, radius)
  x_offset, y_offset = nil, nil
  rad = radius.to_f

  begin
    x_offset = rand(-rad..rad)
    y_offset = rand(-rad..rad)
  end until Math.hypot(x_offset, y_offset) < radius

  [x_origin + x_offset, y_origin + y_offset] 
end

答案 2 :(得分:1)

我建议生成随机半径和随机角度。然后,您可以使用这些来生成Math.sinMath.cos的坐标。

def location(max_radius)
    # 0 to max radius.
    # Using a range ensures max_radius is included.
    radius = Random.rand(0.0..max_radius)

    # 0 to 2 Pi excluding 2 Pi because that's just 0.
    # Using Random.rand() because Kernel#rand() does not honor floats.
    radians = Random.rand(2 * Math::PI)

    # Math.cos/sin work in radians, not degrees.
    x = radius * Math.cos(radians)
    y = radius * Math.sin(radians)

    return [x, y]
end

我会将此转换为lat / long并为您添加中心。

我真正建议的是找到一个支持&#34等操作的几何库;在这个形状内给我一个随机点&#34;,&#34;这个点就在这个形状内#34;并且会为你做转/长转换,因为这些东西很容易出错。

您可以在Ruby geometry gem之上构建,它为您提供基本形状的类。或者,如果您的数据位于数据库中,则许多数据支持几何类型,例如PostgreSQL's geometry types,功能更强大的PostGIS add-on,甚至MySQL has spatial data types

答案 3 :(得分:1)

有一颗宝石可以满足您的需求。

https://github.com/sauloperez/random-location

只需安装gem并在代码中使用它:

gem install random-location
require 'random-location'
RandomLocation.near_by(41.38506, 2.17340, 10000)