Merge pull request #370 from simoami/master
Fixed the haversine formula to calculate distance between 2 points
This commit is contained in:
commit
efe4601fdb
48
lib/geo.js
48
lib/geo.js
|
@ -111,7 +111,11 @@ exports.GeoPoint = GeoPoint;
|
|||
*
|
||||
* @options {Object} Options Object with two Number properties: lat and long.
|
||||
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
|
||||
* @property {Number} lng The longitude point in degrees. Range: -90 to 90.
|
||||
* @property {Number} lng The longitude point in degrees. Range: -180 to 180.
|
||||
*
|
||||
* @options {Array} Options Array with two Number entries: [lat,long].
|
||||
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
|
||||
* @property {Number} lng The longitude point in degrees. Range: -180 to 180.
|
||||
*/
|
||||
|
||||
function GeoPoint(data) {
|
||||
|
@ -119,14 +123,23 @@ function GeoPoint(data) {
|
|||
return new GeoPoint(data);
|
||||
}
|
||||
|
||||
if(arguments.length === 2) {
|
||||
data = {
|
||||
lat: arguments[0],
|
||||
lng: arguments[1]
|
||||
};
|
||||
}
|
||||
|
||||
assert(Array.isArray(data) || typeof data === 'object' || typeof data === 'string', 'must provide valid geo-coordinates array [lat, lng] or object or a "lat, lng" string');
|
||||
|
||||
if (typeof data === 'string') {
|
||||
data = data.split(/,\s*/);
|
||||
assert(data.length === 2, 'must provide a string "lng,lat" creating a GeoPoint with a string');
|
||||
assert(data.length === 2, 'must provide a string "lat,lng" creating a GeoPoint with a string');
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
data = {
|
||||
lng: Number(data[0]),
|
||||
lat: Number(data[1])
|
||||
lat: Number(data[0]),
|
||||
lng: Number(data[1])
|
||||
};
|
||||
} else {
|
||||
data.lng = Number(data.lng);
|
||||
|
@ -177,7 +190,7 @@ GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
|
|||
var y2 = b.lng;
|
||||
|
||||
return geoDistance(x1, y1, x2, y2, options);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the spherical distance to the given point.
|
||||
|
@ -202,26 +215,22 @@ GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
|
|||
|
||||
GeoPoint.prototype.distanceTo = function (point, options) {
|
||||
return GeoPoint.distanceBetween(this, point, options);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple serialization.
|
||||
*/
|
||||
|
||||
GeoPoint.prototype.toString = function () {
|
||||
return this.lng + ',' + this.lat;
|
||||
}
|
||||
return this.lat + ',' + this.lng;
|
||||
};
|
||||
|
||||
/**
|
||||
* @property {Number} PI - Ratio of a circle's circumference to its diameter.
|
||||
* @property {Number} DEG2RAD - Factor to convert degrees to radians.
|
||||
* @property {Number} RAD2DEG - Factor to convert radians to degrees.
|
||||
* @property {Object} EARTH_RADIUS - Radius of the earth.
|
||||
*/
|
||||
|
||||
// ratio of a circle's circumference to its diameter
|
||||
var PI = 3.1415926535897932384626433832795;
|
||||
|
||||
// factor to convert degrees to radians
|
||||
var DEG2RAD = 0.01745329252;
|
||||
|
||||
|
@ -239,18 +248,23 @@ var EARTH_RADIUS = {
|
|||
};
|
||||
|
||||
function geoDistance(x1, y1, x2, y2, options) {
|
||||
|
||||
var type = (options && options.type) || 'miles';
|
||||
|
||||
// Convert to radians
|
||||
x1 = x1 * DEG2RAD;
|
||||
y1 = y1 * DEG2RAD;
|
||||
x2 = x2 * DEG2RAD;
|
||||
y2 = y2 * DEG2RAD;
|
||||
|
||||
var a = Math.pow(Math.sin(( y2 - y1 ) / 2.0), 2);
|
||||
var b = Math.pow(Math.sin(( x2 - x1 ) / 2.0), 2);
|
||||
var c = Math.sqrt(a + Math.cos(y2) * Math.cos(y1) * b);
|
||||
// use the haversine formula to calculate distance for any 2 points on a sphere.
|
||||
// ref http://en.wikipedia.org/wiki/Haversine_formula
|
||||
var haversine = function(a) {
|
||||
return Math.pow(Math.sin(a / 2.0), 2);
|
||||
};
|
||||
|
||||
var type = (options && options.type) || 'miles';
|
||||
var f = Math.sqrt(haversine(x2 - x1) + Math.cos(x2) * Math.cos(x1) * haversine(y2 - y1));
|
||||
|
||||
return 2 * Math.asin(c) * EARTH_RADIUS[type];
|
||||
return 2 * Math.asin(f) * EARTH_RADIUS[type];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*global describe,it*/
|
||||
/*jshint expr:true */
|
||||
|
||||
require('should');
|
||||
|
||||
var GeoPoint = require('../lib/geo').GeoPoint;
|
||||
|
||||
describe('GeoPoint', function () {
|
||||
|
||||
describe('constructor', function() {
|
||||
|
||||
it('should support a valid array', function () {
|
||||
var point = new GeoPoint([-34, 150]);
|
||||
|
||||
point.lat.should.equal(-34);
|
||||
point.lng.should.equal(150);
|
||||
});
|
||||
|
||||
it('should support a valid object', function () {
|
||||
var point = new GeoPoint({ lat: -34, lng: 150 });
|
||||
|
||||
point.lat.should.equal(-34);
|
||||
point.lng.should.equal(150);
|
||||
});
|
||||
|
||||
it('should support valid string geo coordinates', function () {
|
||||
var point = new GeoPoint('-34,150');
|
||||
|
||||
point.lat.should.equal(-34);
|
||||
point.lng.should.equal(150);
|
||||
});
|
||||
|
||||
it('should support coordinates as inline parameters', function () {
|
||||
var point = new GeoPoint(-34, 150);
|
||||
|
||||
point.lat.should.equal(-34);
|
||||
point.lng.should.equal(150);
|
||||
});
|
||||
|
||||
it('should reject invalid parameters', function () {
|
||||
/*jshint -W024 */
|
||||
var fn = function() {
|
||||
new GeoPoint('150,-34');
|
||||
};
|
||||
fn.should.throw();
|
||||
|
||||
fn = function() {
|
||||
new GeoPoint('invalid_string');
|
||||
};
|
||||
fn.should.throw();
|
||||
|
||||
fn = function() {
|
||||
new GeoPoint([150, -34]);
|
||||
};
|
||||
fn.should.throw();
|
||||
|
||||
fn = function() {
|
||||
new GeoPoint({
|
||||
lat: 150,
|
||||
lng: null
|
||||
});
|
||||
};
|
||||
fn.should.throw();
|
||||
|
||||
fn = function() {
|
||||
new GeoPoint(150, -34);
|
||||
};
|
||||
fn.should.throw();
|
||||
|
||||
fn = function() {
|
||||
new GeoPoint();
|
||||
};
|
||||
fn.should.throw();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('toString()', function() {
|
||||
|
||||
it('should return a string in the form "lat,lng"', function() {
|
||||
|
||||
var point = new GeoPoint({ lat: -34, lng: 150 });
|
||||
point.toString().should.equal('-34,150');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('distance calculation between two points', function () {
|
||||
|
||||
var here = new GeoPoint({ lat: 40.77492964101182, lng: -73.90950187151662 });
|
||||
var there = new GeoPoint({ lat: 40.7753227, lng: -73.909217 });
|
||||
|
||||
it('should return value in miles by default', function () {
|
||||
|
||||
var distance = GeoPoint.distanceBetween(here, there);
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(0.03097916611592679);
|
||||
});
|
||||
|
||||
it('should return value using specified unit', function () {
|
||||
|
||||
/* Supported units:
|
||||
* - `radians`
|
||||
* - `kilometers`
|
||||
* - `meters`
|
||||
* - `miles`
|
||||
* - `feet`
|
||||
* - `degrees`
|
||||
*/
|
||||
|
||||
var distance = here.distanceTo(there, { type: 'radians'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(0.000007825491914348416);
|
||||
|
||||
distance = here.distanceTo(there, { type: 'kilometers'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(0.04985613511367009);
|
||||
|
||||
distance = here.distanceTo(there, { type: 'meters'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(49.856135113670085);
|
||||
|
||||
distance = here.distanceTo(there, { type: 'miles'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(0.03097916611592679);
|
||||
|
||||
distance = here.distanceTo(there, { type: 'feet'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(163.56999709209347);
|
||||
|
||||
distance = here.distanceTo(there, { type: 'degrees'});
|
||||
distance.should.be.a.Number;
|
||||
distance.should.equal(0.0004483676593058972);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue