Cyril Duchon-Doris
Cyril Duchon-Doris

Reputation: 13949

Generate random coordinates outside and within a given radius from origin

I need to generate a spec for my search engine to test that, when I specify an origin point and a radius, results beyond results of my source origin are not included in the response, while radius within the given radius in kilometers are included.

Given the origin and the radius, I want to test two "extreme" cases where I create two test data points,

For instance if my radius is 10km, I'd like to generate a point 10.1km away and 9.9 km away from the source.

Then, I want to run many tests where I pick random points on earth (either within or outside the radius) to check that all corner cases are handled well by my search engine

I am checking the code actually works with Geocoder.distance_between

before do
  # Ensure our example is correct
  expect(
    Geocoder::Calculations.distance_between(origin, point_outside_radius_of_origin)
  ).to be > radius_in_km

  expect(
    Geocoder::Calculations.distance_between(origin, point_within_radius_of_origin)
  ).to be < radius_in_km
end

For instance the following fixed example passes

Using a fixed example of Paris

let(:origin) { [48.856614, 2.3522219] } # Geocoder.coordinates('Paris')
let(:radius_in_km) { 10 }
let(:point_within_radius_of_origin) { [48.801148, 2.429443] } # Geocoder.coordinates('Maisons-Alfort')
let(:point_outside_radius_of_origin) { [48.790367, 2.455572] } # Geocoder.coordinates('Créteil')

So I'm trying to implement function to generate random points from an origin.

From https://stackoverflow.com/a/43202522/2832282 I was somehow able to generate a point within the given radius : (this function seems to work perfectly)

# Thanks to https://stackoverflow.com/a/43202522/2832282
#
# @param lon [Float]
# @param lat [Float]
# @param max_radius [FLoat] in km
#
# @return [Pair<Float>] [Lng, lat]
def random_point_within_radius_of_origin(lng:, lat:, max_radius:)
  dx, dy = Utility.random_point_in_disk(max_radius)
  random_lat = lat + dy / OneDegree
  random_lng = lng + dx / ( OneDegree * Math::cos(lat * Math::PI / 180) )
  [random_lng, random_lat]
end
# @param max_radius [Float] Distance in km
#
# @return [Pair<Float>]
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

I need to implement the function below to generate a random point outside the radius from the origin (I don't really care whether uniformly distributed or not) outside the given disk. I guess I could generate a fake point and retry until I hit the goal off.

Note that if you have some code that works without specifying a max_radius this is even better (I basically just want to exclude the coordinates generated by the aforementionned function)

# @param lon [Float] Longitude of origin
# @param lat [Float] Latitude of origin
# @param max_radius [FLoat] Max radius from origin in km
# @param min_radius [FLoat] Min radius from origin in km
#
# @return [Pair<Float>] [Lng, lat] of some random point between min_radius and max_radius of origin
def random_point_within_radius_band_of_origin(lng:, lat:, max_radius:, min_radius:)
  # What this SO Question is about
end

Upvotes: 0

Views: 441

Answers (2)

Ralf Stubner
Ralf Stubner

Reputation: 26823

It is straight forward to combine Severin's code for generating a random point within a band with the code from random_point_within_radius_of_origin:

# @param min_radius [Float] Minimum radius, in km
# @param max_radius [Float] Maximum radius, in km
#
# @return [Pair<Float>]
def random_point_in_band(min_radius, max_radius)
  r     = min_radius + (max_radius - min_radius) *  Math.sqrt(rand)
  theta = 2.0 * Math::PI * rand
  [r * Math.cos(theta), r * Math.sin(theta)]
end

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

# @param lon [Float] Longitude of origin
# @param lat [Float] Latitude of origin
# @param max_radius [FLoat] Max radius from origin in km
# @param min_radius [FLoat] Min radius from origin in km
#
# @return [Pair<Float>] [Lng, lat] of some random point between min_radius and max_radius of origin
def random_point_within_radius_band_of_origin(lng:, lat:, max_radius:, min_radius:)
  dx, dy = random_point_in_band(min_radius, max_radius)
  random_lat = lat + dy / OneDegree
  random_lng = lng + dx / ( OneDegree * Math::cos(lat * Math::PI / 180) )
  [random_lng, random_lat]
end

Upvotes: 0

Severin Pappadeux
Severin Pappadeux

Reputation: 20080

If you need code to generate uniform random point in the radial band, here it is (untested!)

# @param min_radius [Float] Minimum radius, in km
# @param max_radius [Float] Maximum radius, in km
#
# @return [Pair<Float>]
def random_point_in_band(min_radius, max_radius)
  r     = min_radius + (max_radius - min_radius) *  Math.sqrt(rand)
  theta = 2.0 * Math::PI * rand
  [r * Math.cos(theta), r * Math.sin(theta)]
end

Upvotes: 1

Related Questions