UNPKG

random-location

Version:

Random coordinates within a circle (or on a circumference) given a center point and radius.

118 lines (94 loc) 3.83 kB
"use strict"; const EARTH_RADIUS = 6371000; // meters const DEG_TO_RAD = Math.PI / 180.0; const THREE_PI = Math.PI * 3; const TWO_PI = Math.PI * 2; const toRadians = deg => deg * DEG_TO_RAD; const toDegrees = rad => rad / DEG_TO_RAD; /* Given a centerPoint C and a radius R, returns a random point that is on the circumference defined by C and R. centerPoint C is of type { latitude: A, longitude: B } Where -90 <= A <= 90 and -180 <= B <= 180. radius R is in meters. Based on: http://www.movable-type.co.uk/scripts/latlong.html#destPoint */ const randomCircumferencePoint = (centerPoint, radius, randomFn = Math.random) => { const sinLat = Math.sin(toRadians(centerPoint.latitude)); const cosLat = Math.cos(toRadians(centerPoint.latitude)); // Random bearing (direction out 360 degrees) const bearing = randomFn() * TWO_PI; const sinBearing = Math.sin(bearing); const cosBearing = Math.cos(bearing); // Theta is the approximated angular distance const theta = radius / EARTH_RADIUS; const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); let rLatitude, rLongitude; rLatitude = Math.asin(sinLat * cosTheta + cosLat * sinTheta * cosBearing); rLongitude = toRadians(centerPoint.longitude) + Math.atan2(sinBearing * sinTheta * cosLat, cosTheta - sinLat * Math.sin(rLatitude)); // Normalize longitude L such that -PI < L < +PI rLongitude = (rLongitude + THREE_PI) % TWO_PI - Math.PI; return { latitude: toDegrees(rLatitude), longitude: toDegrees(rLongitude) }; }; /* Given a centerPoint C and a radius R, returns a random point that is inside the circle defined by C and R. centerPoint C is of type { latitude: A, longitude: B } Where -90 <= A <= 90 and -180 <= B <= 180. radius R is in meters. */ const randomCirclePoint = (centerPoint, radius, randomFn = Math.random) => { // http://mathworld.wolfram.com/DiskPointPicking.html return randomCircumferencePoint(centerPoint, Math.sqrt(randomFn()) * radius, randomFn); }; /** * Returns a random point in a region bounded by two concentric circles (annulus). * * centerPoint - the center point of both circles. * innerRadius - the radius of the smaller circle. * outerRadius - the radius of the larger circle * randomFn - *optional* A random function. Output is >=0 and <=1. Allows * usage of seeded random number generators - i.e. allows us to predict * generated random coordiantes. default is Math.random() * */ const randomAnnulusPoint = (centerPoint, innerRadius, outerRadius, randomFn = Math.random) => { if (innerRadius >= outerRadius) { throw new Error(`innerRadius (${innerRadius}) should be smaller than outerRadius (${outerRadius})`); } const deltaRadius = outerRadius - innerRadius; return randomCircumferencePoint(centerPoint, innerRadius + Math.sqrt(randomFn()) * deltaRadius, randomFn); }; /* Returns the distance in meters between two points P1 and P2. P1 and P2 are of type { latitude: A, longitude: B } Where -90 <= A <= 90 and -180 <= B <= 180. Basically it is the Haversine distance function. Based on: http://www.movable-type.co.uk/scripts/latlong.html */ const distance = (P1, P2) => { const rP1 = { latitude: toRadians(P1.latitude), longitude: toRadians(P1.longitude) }; const rP2 = { latitude: toRadians(P2.latitude), longitude: toRadians(P2.longitude) }; const D = { latitude: Math.sin((rP2.latitude - rP1.latitude) / 2), longitude: Math.sin((rP2.longitude - rP1.longitude) / 2) }; const A = D.latitude * D.latitude + D.longitude * D.longitude * Math.cos(rP1.latitude) * Math.cos(rP2.latitude); const C = 2 * Math.atan2(Math.sqrt(A), Math.sqrt(1 - A)); return EARTH_RADIUS * C; }; const haversine = distance; module.exports = { distance, haversine, randomCircumferencePoint, randomCirclePoint, randomAnnulusPoint };