From ef11ab85c1277d62794289ad2141a62f2285f424 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Thu, 5 May 2016 14:39:02 -0400 Subject: Added address.nearbyLocation() method Returns a random GPS coordinate located within a distance of the given GPS coordinate --- lib/address.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/address.js b/lib/address.js index 6a9e3beb..dec80d8f 100644 --- a/lib/address.js +++ b/lib/address.js @@ -101,6 +101,50 @@ var address = { longitude: function () { return (faker.random.number(360 * 10000) / 10000.0 - 180.0).toFixed(4); + }, + + nearbyLocation: function(location, radius, isMetric) { + function randomFloat(min, max) { + return Math.random() * (max-min) + min; + } + function degreesToRadians(degrees) { + return degrees * (Math.PI/180.0); + } + function radiansToDegrees(radians) { + return radians * (180.0/Math.PI); + } + function kilometersToMiles(miles) { + return miles * 0.621371; + } + function boundaryLocation(location, bearing, distance, isMetric) { + var R = 6378.137; // Radius of the Earth (http://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html) + var d = isMetric ? distance : kilometersToMiles(distance); // Distance in km + + lat1 = degreesToRadians(location[0]); //Current lat point converted to radians + lon1 = degreesToRadians(location[1]); //Current long point converted to radians + + lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/R) + + Math.cos(lat1) * Math.sin(d/R) * Math.cos(bearing)); + + lon2 = lon1 + Math.atan2( + Math.sin(bearing) * Math.sin(d/R) * Math.cos(lat1), + Math.cos(d/R) - Math.sin(lat1) * Math.sin(lat2)); + + return [radiansToDegrees(lat2), radiansToDegrees(lon2)]; + } + + location = location || [faker.latitude(), faker.longitude()] + radius = radius || 10.0; + isMetric = isMetric || false; + + // This is a heuristic to approximate the circular region defined by location and radius. + topLeftBoundary = boundaryLocation(location, degreesToRadians(45), radius, isMetric); + bottomRightBoundary = boundaryLocation(location, degreesToRadians(45), -radius, isMetric); + + randLat = randomFloat(topLeftBoundary[0], bottomRightBoundary[0]).toFixed(4); + randLong = randomFloat(topLeftBoundary[1], bottomRightBoundary[1]).toFixed(4); + + return [randLat, randLong]; } }; -- cgit v1.2.3 From ec5e7ca44299c7e19721d0de8dde499cab7021e8 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Thu, 5 May 2016 14:43:08 -0400 Subject: Updated readme Included mention of nearbyLocation method --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 4eb761a7..ee528a0b 100644 --- a/Readme.md +++ b/Readme.md @@ -44,7 +44,7 @@ Read further for complete list of locales. ## API - + ## Tests npm install . -- cgit v1.2.3 From e8f51dcddd2586fcce12635850a6692b414869ef Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Thu, 5 May 2016 14:52:13 -0400 Subject: Use term 'coordinate' instead of 'location' --- lib/address.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/address.js b/lib/address.js index dec80d8f..6d9748fd 100644 --- a/lib/address.js +++ b/lib/address.js @@ -103,7 +103,7 @@ var address = { return (faker.random.number(360 * 10000) / 10000.0 - 180.0).toFixed(4); }, - nearbyLocation: function(location, radius, isMetric) { + nearbyGPSCoordinate: function(coordinate, radius, isMetric) { function randomFloat(min, max) { return Math.random() * (max-min) + min; } @@ -116,12 +116,12 @@ var address = { function kilometersToMiles(miles) { return miles * 0.621371; } - function boundaryLocation(location, bearing, distance, isMetric) { + function boundaryCoordinate(coordinate, bearing, distance, isMetric) { var R = 6378.137; // Radius of the Earth (http://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html) var d = isMetric ? distance : kilometersToMiles(distance); // Distance in km - lat1 = degreesToRadians(location[0]); //Current lat point converted to radians - lon1 = degreesToRadians(location[1]); //Current long point converted to radians + lat1 = degreesToRadians(coordinate[0]); //Current lat point converted to radians + lon1 = degreesToRadians(coordinate[1]); //Current long point converted to radians lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/R) + Math.cos(lat1) * Math.sin(d/R) * Math.cos(bearing)); @@ -137,9 +137,9 @@ var address = { radius = radius || 10.0; isMetric = isMetric || false; - // This is a heuristic to approximate the circular region defined by location and radius. - topLeftBoundary = boundaryLocation(location, degreesToRadians(45), radius, isMetric); - bottomRightBoundary = boundaryLocation(location, degreesToRadians(45), -radius, isMetric); + // This is a heuristic to approximate the circular region defined by coordinate and radius. + topLeftBoundary = boundaryCoordinate(coordinate, degreesToRadians(45), radius, isMetric); + bottomRightBoundary = boundaryCoordinate(coordinate, degreesToRadians(45), -radius, isMetric); randLat = randomFloat(topLeftBoundary[0], bottomRightBoundary[0]).toFixed(4); randLong = randomFloat(topLeftBoundary[1], bottomRightBoundary[1]).toFixed(4); -- cgit v1.2.3 From bec87b1acb708cba7b13173d3e240ce9dfb4bb27 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Thu, 5 May 2016 14:52:29 -0400 Subject: If no input params return random coordinate --- lib/address.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/address.js b/lib/address.js index 6d9748fd..684914ce 100644 --- a/lib/address.js +++ b/lib/address.js @@ -133,7 +133,10 @@ var address = { return [radiansToDegrees(lat2), radiansToDegrees(lon2)]; } - location = location || [faker.latitude(), faker.longitude()] + // If there is no coordinate, the best we can do is return a random GPS coordinate. + if (coordinate === undefined) { + return [this.latitude(), this.longitude()] + } radius = radius || 10.0; isMetric = isMetric || false; -- cgit v1.2.3 From 43495cc4c07061610d318eb2b31e6b7075810f33 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Fri, 6 May 2016 01:57:53 -0400 Subject: Fixed bug where variables were being leaked to global namespace --- lib/address.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/address.js b/lib/address.js index 684914ce..5fa924d4 100644 --- a/lib/address.js +++ b/lib/address.js @@ -120,13 +120,13 @@ var address = { var R = 6378.137; // Radius of the Earth (http://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html) var d = isMetric ? distance : kilometersToMiles(distance); // Distance in km - lat1 = degreesToRadians(coordinate[0]); //Current lat point converted to radians - lon1 = degreesToRadians(coordinate[1]); //Current long point converted to radians + var lat1 = degreesToRadians(coordinate[0]); //Current lat point converted to radians + var lon1 = degreesToRadians(coordinate[1]); //Current long point converted to radians - lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/R) + + var lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/R) + Math.cos(lat1) * Math.sin(d/R) * Math.cos(bearing)); - lon2 = lon1 + Math.atan2( + var lon2 = lon1 + Math.atan2( Math.sin(bearing) * Math.sin(d/R) * Math.cos(lat1), Math.cos(d/R) - Math.sin(lat1) * Math.sin(lat2)); -- cgit v1.2.3 From 0bfeb93c594ef31d4002eac6356a81b9e19fbb80 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Fri, 6 May 2016 01:58:21 -0400 Subject: Ensure longitude points are within range [-180, 180] --- lib/address.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/address.js b/lib/address.js index 5fa924d4..f6e5be12 100644 --- a/lib/address.js +++ b/lib/address.js @@ -130,6 +130,13 @@ var address = { Math.sin(bearing) * Math.sin(d/R) * Math.cos(lat1), Math.cos(d/R) - Math.sin(lat1) * Math.sin(lat2)); + // Keep longitude in range [-180, 180] + if (lon2 > degreesToRadians(180)) { + lon2 = lon2 - degreesToRadians(360); + } else if (lon2 < degreesToRadians(-180)) { + lon2 = lon2 + degreesToRadians(360); + } + return [radiansToDegrees(lat2), radiansToDegrees(lon2)]; } -- cgit v1.2.3 From 08e264b10ffceaf7a389753f56331d09a68eafe2 Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Fri, 6 May 2016 01:58:48 -0400 Subject: Using different heuristic for producing a random GPS coordinate this approach will not result in a uniform distribution, but will instead concentrate points in the center. --- lib/address.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/address.js b/lib/address.js index f6e5be12..dde53b0d 100644 --- a/lib/address.js +++ b/lib/address.js @@ -116,7 +116,7 @@ var address = { function kilometersToMiles(miles) { return miles * 0.621371; } - function boundaryCoordinate(coordinate, bearing, distance, isMetric) { + function coordinateWithOffset(coordinate, bearing, distance, isMetric) { var R = 6378.137; // Radius of the Earth (http://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html) var d = isMetric ? distance : kilometersToMiles(distance); // Distance in km @@ -147,14 +147,12 @@ var address = { radius = radius || 10.0; isMetric = isMetric || false; - // This is a heuristic to approximate the circular region defined by coordinate and radius. - topLeftBoundary = boundaryCoordinate(coordinate, degreesToRadians(45), radius, isMetric); - bottomRightBoundary = boundaryCoordinate(coordinate, degreesToRadians(45), -radius, isMetric); + // TODO: implement either a gaussian/uniform distribution of points in cicular region. + // Possibly include param to function that allows user to choose between distributions. - randLat = randomFloat(topLeftBoundary[0], bottomRightBoundary[0]).toFixed(4); - randLong = randomFloat(topLeftBoundary[1], bottomRightBoundary[1]).toFixed(4); - - return [randLat, randLong]; + // This approach will likely result in a higher density of points near the center. + var randomCoord = coordinateWithOffset(coordinate, degreesToRadians(Math.random() * 360.0), radius, isMetric); + return [randomCoord[0].toFixed(4), randomCoord[1].toFixed(4)]; } }; -- cgit v1.2.3 From 58936caa649a7b1fbb296ed66b5e1c3af656ea9a Mon Sep 17 00:00:00 2001 From: Vivek Seth Date: Fri, 6 May 2016 01:59:40 -0400 Subject: Added unit test for nearbyGPSCoordinate --- test/address.unit.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/address.unit.js b/test/address.unit.js index b03a3be4..dea801e7 100644 --- a/test/address.unit.js +++ b/test/address.unit.js @@ -236,4 +236,52 @@ describe("address.js", function () { }); }); + describe("nearbyGPSCoordinate()", function () { + it("returns random gps coordinate within a distance of another one", function () { + function haversine(lat1, lon1, lat2, lon2, isMetric) { + function degreesToRadians(degrees) { + return degrees * (Math.PI/180.0); + } + function kilometersToMiles(miles) { + return miles * 0.621371; + } + var R = 6378.137; + var dLat = degreesToRadians(lat2-lat1); + var dLon = degreesToRadians(lon2-lon1); + var a = Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(degreesToRadians(lat1)) * Math.cos(degreesToRadians(lat2)) + * Math.sin(dLon/2) * Math.sin(dLon/2); + var distance = R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + return isMetric ? distance : kilometersToMiles(distance); + } + for (var i = 0; i < 10000; i++) { + var latFloat1 = parseFloat(faker.address.latitude()); + var lonFloat1 = parseFloat(faker.address.longitude()); + var radius = (Math.random() * 99) + 1; // range of [1, 100) + var isMetric = (Math.round(Math.random()) == 1); + + var coordinate = faker.address.nearbyGPSCoordinate([latFloat1, lonFloat1], radius, isMetric); + assert.ok(coordinate.length === 2); + assert.ok(typeof coordinate[0] === 'string'); + assert.ok(typeof coordinate[1] === 'string'); + + var latFloat2 = parseFloat(coordinate[0]); + assert.ok(latFloat2 >= -90.0); + assert.ok(latFloat2 <= 90.0); + + var lonFloat2 = parseFloat(coordinate[1]); + assert.ok(lonFloat2 >= -180.0); + assert.ok(lonFloat2 <= 180.0); + + // Due to floating point math, and constants that are not extremely precise, + // returned points will not be strictly within the given radius of the input + // coordinate. Using a error of 1.0 to compensate. + var error = 1.0; + var actualDistance = haversine(latFloat1, lonFloat1, latFloat2, lonFloat2, isMetric); + assert.ok(actualDistance <= (radius + error)); + } + }); + }); + }); -- cgit v1.2.3