Skip to content

Commit

Permalink
Add alongTrackDistanceTo()
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisveness committed May 7, 2017
1 parent f2d30ec commit c3467a7
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 11 deletions.
37 changes: 35 additions & 2 deletions latlon-spherical.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,42 @@ LatLon.prototype.crossTrackDistanceTo = function(pathStart, pathEnd, radius) {
var θ13 = pathStart.bearingTo(this).toRadians();
var θ12 = pathStart.bearingTo(pathEnd).toRadians();

var δ = Math.asin( Math.sin(δ13) * Math.sin(θ13-θ12) );
var δxt = Math.asin(Math.sin(δ13) * Math.sin(θ13-θ12));

return δ * R;
return δxt * R;
};


/**
* Returns how far ‘this’ point is along a path from from start-point, heading towards end-point.
* That is, if a perpendicular is drawn from ‘this’ point to the (great circle) path, the along-track
* distance is the distance from the start point to where the perpendicular crosses the path.
*
* @param {LatLon} pathStart - Start point of great circle path.
* @param {LatLon} pathEnd - End point of great circle path.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {number} Distance along great circle to point nearest ‘this’ point.
*
* @example
* var pCurrent = new LatLon(53.2611, -0.7972);
* var p1 = new LatLon(53.3206, -1.7297);
* var p2 = new LatLon(53.1887, 0.1334);
* var d = pCurrent.alongTrackDistanceTo(p1, p2); // 62.331 km
*/
LatLon.prototype.alongTrackDistanceTo = function(pathStart, pathEnd, radius) {
if (!(pathStart instanceof LatLon)) throw new TypeError('pathStart is not LatLon object');
if (!(pathEnd instanceof LatLon)) throw new TypeError('pathEnd is not LatLon object');
var R = (radius === undefined) ? 6371e3 : Number(radius);

var δ13 = pathStart.distanceTo(this, R) / R;
var θ13 = pathStart.bearingTo(this).toRadians();
var θ12 = pathStart.bearingTo(pathEnd).toRadians();

var δxt = Math.asin(Math.sin(δ13) * Math.sin(θ13-θ12));

var δat = Math.acos(Math.cos(δ13) / Math.abs(Math.cos(δxt)));

return δat*Math.sign(Math.cos(θ12-θ13)) * R;
};


Expand Down
41 changes: 39 additions & 2 deletions latlon-vectors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vector-based spherical geodetic (latitude/longitude) functions (c) Chris Veness 2011-2016 */
/* Vector-based spherical geodetic (latitude/longitude) functions (c) Chris Veness 2011-2017 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/latlong-vectors.html */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-nvector-spherical.html */
Expand Down Expand Up @@ -154,7 +154,7 @@ LatLon.prototype.distanceTo = function(point, radius) {
var p1 = this.toVector();
var p2 = point.toVector();

var δ = p1.angleTo(p2);
var δ = p1.angleTo(p2); // δ = atan2(|p₁×p₂|, p₁·p₂)
var d = δ * radius;

return d;
Expand Down Expand Up @@ -442,6 +442,43 @@ LatLon.prototype.crossTrackDistanceTo = function(pathStart, pathBrngEnd, radius)
};


/**
* Returns how far ‘this’ point is along a path from from start-point, heading on bearing or towards
* end-point. That is, if a perpendicular is drawn from ‘this’ point to the (great circle) path, the
* along-track distance is the distance from the start point to where the perpendicular crosses the
* path.
*
* @param {LatLon} pathStart - Start point of great circle path.
* @param {LatLon|number} pathBrngEnd - End point of great circle path or initial bearing from great circle start point.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {number} Distance along great circle to point nearest ‘this’ point.
*
* @example
* var pCurrent = new LatLon(53.2611, -0.7972);
* var p1 = new LatLon(53.3206, -1.7297);
* var p2 = new LatLon(53.1887, 0.1334);
* var d = pCurrent.alongTrackDistanceTo(p1, p2); // 62.331 km
*/
LatLon.prototype.alongTrackDistanceTo = function(pathStart, pathBrngEnd, radius) {
if (!(pathStart instanceof LatLon)) throw new TypeError('pathStart is not LatLon object');
var R = (radius === undefined) ? 6371e3 : Number(radius);

var p = this.toVector();

var gc = pathBrngEnd instanceof LatLon // (note JavaScript is not good at method overloading)
? pathStart.toVector().cross(pathBrngEnd.toVector()) // great circle defined by two points
: pathStart.greatCircle(Number(pathBrngEnd)); // great circle defined by point + bearing

var pat = gc.cross(p).cross(gc); // along-track point c × p × c

var α = pathStart.toVector().angleTo(pat, gc); // angle between start point and along-track point

var d = α * R;

return d;
};


/**
* Returns closest point on great circle segment between point1 & point2 to ‘this’ point.
*
Expand Down
17 changes: 13 additions & 4 deletions test/latlon-spherical-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,24 @@ describe('latlon-spherical', function() {

var greenwich = new LatLon(51.4778, -0.0015), dist = 7794, brng = 300.7;
test('dest’n', function() { greenwich.destinationPoint(dist, brng).toString('d').should.equal('51.5135°N, 000.0983°W'); });
test('dest’n', function() { greenwich.destinationPoint(dist, brng, 6371e3).toString('d').should.equal('51.5135°N, 000.0983°W'); });
test('dest’n inc R', function() { greenwich.destinationPoint(dist, brng, 6371e3).toString('d').should.equal('51.5135°N, 000.0983°W'); });

var bradwell = new LatLon(53.3206, -1.7297);
test('cross-track', function() { new LatLon(53.2611, -0.7972).crossTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('-307.5'); });
test('along-track', function() { new LatLon(53.2611, -0.7972).alongTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('6.233e+4'); });

test('cross-track p', function() { LatLon(10, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+6'); });
test('cross-track NE', function() { LatLon( 1, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });
test('cross-track SE', function() { LatLon(-1, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('cross-track SW?', function() { LatLon(-1, -1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('cross-track NW?', function() { LatLon( 1, -1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });

test('cross-track err', function() { LatLon(10, 1).crossTrackDistanceTo.bind(LatLon, false, LatLon(0, 2)).should.throw(TypeError); });
test('cross-track err', function() { LatLon(10, 1).crossTrackDistanceTo.bind(LatLon, LatLon(0, 0), false).should.throw(TypeError); });
test('along-track NE', function() { LatLon( 1, 1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('along-track SE', function() { LatLon(-1, 1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('along-track SW', function() { LatLon(-1, -1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });
test('along-track NW', function() { LatLon( 1, -1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });

test('cross-track err', function() { LatLon(1, 1).crossTrackDistanceTo.bind(LatLon, false, LatLon(0, 2)).should.throw(TypeError); });
test('cross-track err', function() { LatLon(1, 1).crossTrackDistanceTo.bind(LatLon, LatLon(0, 0), false).should.throw(TypeError); });

test('Clairaut 0°', function() { new LatLon(0,0).maxLatitude( 0).should.equal(90); });
test('Clairaut 1°', function() { new LatLon(0,0).maxLatitude( 1).should.equal(89); });
Expand Down
24 changes: 21 additions & 3 deletions test/latlon-vectors-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,38 @@ describe('latlon-vectors', function() {
var cambg = new LatLon(52.205, 0.119), paris = new LatLon(48.857, 2.351);
test('distance', function() { cambg.distanceTo(paris).toPrecision(4).should.equal('4.043e+5'); });
test('distance (miles)', function() { cambg.distanceTo(paris, 3959).toPrecision(4).should.equal('251.2'); });
test('distance err', function() { cambg.distanceTo.bind(LatLon, 'here').should.throw(TypeError); });
test('initial bearing', function() { cambg.bearingTo(paris).toFixed(1).should.equal('156.2'); });
test('initial brng err', function() { cambg.bearingTo.bind(LatLon, 999).should.throw(TypeError); });
test('midpoint', function() { cambg.midpointTo(paris).toString('d').should.equal('50.5363°N, 001.2746°E'); });
test('midpoint err', function() { cambg.midpointTo.bind(LatLon, true).should.throw(TypeError); });
test('int.point', function() { cambg.intermediatePointTo(paris, 0.25).toString('d').should.equal('51.3721°N, 000.7073°E'); });
test('int.point err', function() { cambg.intermediatePointTo.bind(LatLon, 1, 0.5).should.throw(TypeError); });
test('int.point-chord', function() { cambg.intermediatePointOnChordTo(paris, 0.25).toString('d').should.equal('51.3723°N, 000.7072°E'); });

var greenwich = new LatLon(51.4778, -0.0015), dist = 7794, brng = 300.7;
test('dest’n', function() { greenwich.destinationPoint(dist, brng).toString('d').should.equal('51.5135°N, 000.0983°W'); });
test('dest’n inc r', function() { greenwich.destinationPoint(dist, brng, 6371e3).toString('d').should.equal('51.5135°N, 000.0983°W'); });

var bradwell = new LatLon(53.3206, -1.7297);
test('cross-track', function() { new LatLon(53.2611, -0.7972).crossTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('-307.5'); });
test('along-track', function() { new LatLon(53.2611, -0.7972).alongTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('6.233e+4'); });

test('cross-track p', function() { LatLon(10, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+6'); });
test('cross-track we', function() { LatLon(10, 0).crossTrackDistanceTo(LatLon(0, 0), 90).toPrecision(4).should.equal('-1.112e+6'); });
test('cross-track ew', function() { LatLon(10, 0).crossTrackDistanceTo(LatLon(0, 0), 270).toPrecision(4).should.equal('1.112e+6'); });
test('cross-track NE', function() { LatLon(1, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });
test('cross-track SE', function() { LatLon(-1, 1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('cross-track SW?', function() { LatLon(-1, -1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('cross-track NW?', function() { LatLon( 1, -1).crossTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });

test('along-track NE', function() { LatLon( 1, 1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('along-track SE', function() { LatLon(-1, 1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5'); });
test('along-track SW', function() { LatLon(-1, -1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });
test('along-track NW', function() { LatLon( 1, -1).alongTrackDistanceTo(LatLon(0, 0), LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5'); });

test('cross-track err', function() { LatLon(1, 1).crossTrackDistanceTo.bind(LatLon, false, LatLon(0, 2)).should.throw(TypeError); });
test('cross-track err', function() { LatLon(1, 1).crossTrackDistanceTo.bind(LatLon, LatLon(0, 0), false).should.throw(TypeError); });

test('cross-track brng w-e', function() { LatLon(1, 0).crossTrackDistanceTo(LatLon(0, 0), 90).toPrecision(4).should.equal('-1.112e+5'); });
test('cross-track brng e-w', function() { LatLon(1, 0).crossTrackDistanceTo(LatLon(0, 0), 270).toPrecision(4).should.equal('1.112e+5'); });

test('nearest point on segment 1', function() { LatLon(51.0, 1.9).nearestPointOnSegment(LatLon(51.0, 1.0), LatLon(51.0, 2.0)).toString('d').should.equal('51.0004°N, 001.9000°E'); });
test('nearest point on segment 1d', function() { LatLon(51.0, 1.9).nearestPointOnSegment(LatLon(51.0, 1.0), LatLon(51.0, 2.0)).distanceTo(LatLon(51.0, 1.9)).toPrecision(4).should.equal('42.71'); });
Expand Down

0 comments on commit c3467a7

Please sign in to comment.