2

Background:

I am trying to create a coordinates generator for Unit Testing my code. I would like to try to test cases for methods like:

// Check if within 10km.
// Should pass because it is 0 - 10km from location
location.isWithin(generateLocation(location, 10), 10) === true
// Check if within 10km.
// Should fail because it is 20 - 25km from location
location.isWithin(generateLocation(location, 25, 20), 10) === false

I have tried lots of Stackoverflow answers I never found one answer to this problem.

What I have tried:

function generateNearbyLocation(latitude, longitude, max, min = 0) {
  // Convert max in kilometers to degrees
  const radiusInDegrees = (max * 1000) / 111300;

  const u = Math.random();
  const v = Math.random();

  // random radius
  const w = radiusInDegrees * Math.sqrt(u);

  // random angle
  const t = 2 * Math.PI * v;

  const x = w * Math.cos(t);
  const x1 = x / Math.cos(latitude);
  const y1 = w * Math.sin(t);

  // Not working attempt to add Minimum distance
  // const newLatitude = latitude + (min * 1000 / 111300) + y1;
  // const newLongitude = longitude + (min * 1000 / 111300) + x1;

  const newLatitude = latitude + y1;
  const newLongitude = longitude + x1;
  const distance = getDistanceFromLatLonInKm(latitude, longitude, newLatitude, newLongitude);

  return {
    newLatitude,
    newLongitude,
    distance
  };
}

This works but is not accurate. Even with max of 10, it sometimes generate 11km coordinates.

Here's a playground that I made: https://jsbin.com/hakemubogo/edit?js,console


EDIT

I found a much more accurate formula from another answer but still can't create minimum distance.

function generateLocation(latitude, longitude, max, min = 0) {
  const EARTH_RADIUS = 6371; // km
  const DEGREE = EARTH_RADIUS * 2 * Math.PI / 360 * 1000; // 1° latitude in meters

  const r = (max * 1000) * Math.random() ** 0.5;
  const theta = Math.random() * 2 * Math.PI; // random * (360deg in radians)

  const dy = r * Math.sin(theta);
  const dx = r * Math.cos(theta);

  const newLatitude = latitude + dy / DEGREE;
  const newLongitude = longitude + dx / (DEGREE * Math.cos(deg2rad(latitude)));
  const distance = getDistanceFromLatLonInKm(latitude, longitude, newLatitude, newLongitude);

  return {
    newLatitude,
    newLongitude,
    distance
  };
}

1 Answer 1

0

I was able to finally figure it out. According to this answer:

If you have a center (x,y) and you move on the x axis by 30 meters and on the y axis by another 30 meters your distance from the center won't be 30.

It turns out the variable r should be the one I'm adjusting and not the newLatitude and newLongitude.

(max * 1000) * Math.random() ** 0.5 is only generating random [0 - max] value (in a non-uniform way).

Therefore, what I need to add is just add the minimum distance on variable r so it generates [min-max]

const maxKm = max * 1000;
const minKm = min * 1000;
const r = ((maxKm - minKm + 1) * Math.random() ** 0.5) + minKm;

See this answer how to generate number within range: link.

The only thing I added is the ** 0.5 part.

Full code:

// Final Code Here
function generateLocation(latitude, longitude, max, min = 0) {
  if (min > max) {
    throw new Error(`min(${min}) cannot be greater than max(${max})`);
  }

  // earth radius in km
  const EARTH_RADIUS = 6371;

   // 1° latitude in meters
  const DEGREE = EARTH_RADIUS * 2 * Math.PI / 360 * 1000;

  // random distance within [min-max] in km in a non-uniform way
  const maxKm = max * 1000;
  const minKm = min * 1000;
  const r = ((maxKm - minKm + 1) * Math.random() ** 0.5) + minKm;

  // random angle
  const theta = Math.random() * 2 * Math.PI;

  const dy = r * Math.sin(theta);
  const dx = r * Math.cos(theta);

  let newLatitude = latitude + dy / DEGREE;
  let newLongitude = longitude + dx / (DEGREE * Math.cos(deg2rad(latitude)));

  const distance = getDistanceFromLatLonInKm(latitude, longitude, newLatitude, newLongitude);

  return {
    newLatitude,
    newLongitude,
    distance: Math.round(distance)
  };
}

// See https://stackoverflow.com/a/27943/10975709
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km

  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}

console.log(generateLocation(15.143262, 120.583843, 25, 20))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.