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

我正在尝试生成位于半径为5千米的圆内的随机坐标(纬度,长度),其中心点位于某个坐标(x,y)处。 我试图用ruby编码这个,我正在使用该方法,但不知怎的,我得到的结果不在指定的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 

你的代码

 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)) 

现在有趣的部分:如果x在-11之间,则rand(x)只是rand() 。 因此,如果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之间, 313955您的代码相当于:

 lat_offset = lng_offset = 0 

自Ruby 2.4起

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

可以写成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 

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

统一分配

这是与@ 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 

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

几点:

  • 我们通常谈论纬度优先和经度第二,但在GIS中,它通常是首先因为xy之前。
  • 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 

使用gnuplot,这是结果图:

在此处输入图像描述

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

在此处输入图像描述

 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 

我建议生成一个随机半径和一个随机角度。 然后,您可以使用它们生成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并为你添加一个中心。

我真正建议的是找到一个几何库来支持“在这个形状中给我一个随机点”这样的操作,“这个点就在这个形状内”并且会为你做转/长转换因为这个东西很容易得到巧妙的错误。

您可以构建在Ruby几何体gem之上,它为您提供基本形状的类。 或者,如果您的数据位于数据库中,许多支持几何类型,如PostgreSQL的几何类型 ,更强大的PostGIS附加组件 ,甚至MySQL都具有空间数据类型 。

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

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

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

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