Calculate Distance
Calculate Distance
968.9 km (to 4 SF )
Initial bearing:
0090711
Final bearing:
0111631
Midpoint:
542144N, 0043150W
And you can see it on a map (arent those Google guys wonderful!)
Distance
This uses the haversine formula to calculate the great-circle distance between two points that
is, the shortest distance over the earths surface giving an as-the-crow-flies distance between
the points (ignoring any hills they fly over, of course!).
Haversine
formula:
R = 6371000; // metres
1 = lat1.toRadians();
2 = lat2.toRadians();
= (lat2-lat1).toRadians();
= (lon2-lon1).toRadians();
JavaScript:
var d = R * c;
Note in these scripts, I generally use lat/lon for latitude/longitude in degrees, and / for
latitude/longitude in radians having found that mixing degrees & radians is often the easiest
route to head-scratching bugs...
Historical aside: The height of technology for navigators calculations used to be log tables. As
there is no (real) log of a negative number, the versine enabled them to keep trig functions in
positive numbers. Also, the sin(/2) form of the haversine avoided addition (which entailed an
anti-log lookup, the addition, and a log lookup). Printed tables for the haversine/inverse-haversine (and its logarithm, to aid multiplications) saved navigators from squaring sines, computing
square roots, etc arduous and error-prone activities.
The haversine formula remains particularly well-conditioned for numerical computation even at
small distances unlike calculations based on the spherical law of cosines. The versed sine
is 1cos, and the half-versed-sine is (1cos)/2 = sin(/2) as used above. Once widely
used by navigators, it was described by Roger Sinnott in Sky & Telescopemagazine in 1984
(Virtues of the Haversine): Sinnott explained that the angular separation between Mizar and
Alcor in Ursa Major 01149.69 could be accurately calculated on a TRS-80 using the
haversine.
1
For the curious, c is the angular distance in radians, and a is the square of half the chord length
between the points. A (remarkably marginal) performance improvement can be obtained by
factoring out the terms which get squared.
Spherical Law of Cosines
In fact, JavaScript (and most modern computers & languages) use IEEE 754 64-bit floatingpoint numbers, which provide 15 significant figures of precision. By my estimate, with this
precision, the simple spherical law of cosines formula (cos c = cos a cosb + sin a sin b cos C)
gives well-conditioned results down to distances as small as a few metres on the earths
surface. (Note that the geodetic form of the law of cosines is rearranged from the canonical one so that
the latitude can be used directly, rather than thecolatitude).
This makes the simpler law of cosines a reasonable 1-line alternative to the haversine formula for
many geodesy purposes (if not for astronomy). The choice may be driven by coding context,
available trig functions (in different languages), etc and, for very small distances an
equirectangular approximation may be more suitable.
Law of
cosines:
JavaScript:
Excel:
(or with
lat/lon in
degrees):
=ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) +
COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180lon1*PI()/180) ) * 6371000
Equirectangular approximation
If performance is an issue and accuracy less important, for small distances Pythagoras
theorem can be used on anequirectangular projection:
*
Formula
x = cos
y =
d = R x + y
JavaScript:
This uses just one trig and one sqrt function as against half-a-dozen trig functions for cos law,
and 7 trigs + 2 sqrts for haversine. Accuracy is somewhat complex: along meridians there are no
errors, otherwise they depend on distance, bearing, and latitude, but are small enough for many
purposes (and often trivial compared with the spherical approximation itself).
*
Alternatively, the polar coordinate flat-earth formula can be used: using the co-latitudes =
/2 and = /2 , thend = R 1 + 2 2 1 2 cos . Ive not compared
accuracy.
1
Baghdad to Osaka
not a constant bearing!
Bearing
In general, your current heading will vary as you follow a great circle path (orthodrome); the
final heading will differ from the initial heading by varying degrees according to distance and
latitude (if you were to go from say 35N,45E ( Baghdad) to 35N,135E ( Osaka), you
would start on a heading of 60 and end up on a heading of 120!).
This formula is for the initial bearing (sometimes referred to as forward azimuth) which if
followed in a straight line along a great-circle arc will take you from the start point to the end
point:
1
Formula:
JavaScript
:
(all angles
in radians)
Excel:
(all angles
in radians)
=ATAN2(COS(lat1)*SIN(lat2)-SIN(lat1)*COS(lat2)*COS(lon2-lon1),
SIN(lon2-lon1)*COS(lat2))
*note that Excel reverses the arguments to ATAN2 see notes below
Since atan2 returns values in the range - ... + (that is, -180 ... +180), to normalise the result to a
compass bearing (in the range 0 ... 360, with ve values transformed into the range 180 ... 360),
convert to degrees and then use (+360) % 360, where % is (floating point) modulo.
For final bearing, simply take the initial bearing from the end point to the start point and reverse it (using
= (+180) % 360).
Midpoint
This is the half-way point along a great circle path between the two points.
Formula:
B = cos cos
B = cos sin
= atan2( sin + sin , (cos 1 + Bx) + By )
x
JavaScript
:
(all angles
in radians)
Just as the initial bearing may vary from the final bearing, the midpoint may not be located half-way
between latitudes/longitudes; the midpoint between 35N,45E and 35N,135E is around 45N,90E.
km
Destination point:
531118N, 0000800E
Final bearing:
0973052
view map
Formula:
(all angles
in radians)
Excel:
JavaScript
:
(all angles
in radians)
For final bearing, simply take the initial bearing from the end point to the start point and reverse it (using
= (+180) % 360).
Brng 1:
Brng 2:
Point 1:
Point 2:
Intersection point:
Formula:
505427N, 0043031E
if
12
12
12
21
12
21
=
=
=
=
12
12
sin( )
else
>
(
13
+
12
= ( + ) % 2
2
21
23
13
12
13
13
2
3
where
13
13
cos
cos
cos
sin
)
)
)
)
12
13
13
,
,
:
,
,
:
, : intersection point
1
13
13
1st
2nd
point
point
&
&
bearing
bearing
This is a lot simpler using vectors rather than spherical trigonometry: see latlong-vectors.html.
Cross-track distance
Heres a new one: Ive sometimes been asked about distance of a point from a great-circle path
(sometimes called cross track error).
Formula:
13
13
12
13
12
JavaScript:
Here, the great-circle path is identified by a start point and an end point depending on what
initial data youre working from, you can use the formul above to obtain the relevant distance
and bearings. The sign of d tells you which side of the path the third point is on.
xt
The along-track distance, from the start point to the closest point on the path to the third point, is
Formula:
13
xt
JavaScript:
JavaScript:
max
Rhumb lines
A rhumb line (or loxodrome) is a path of constant bearing, which crosses all meridians at the
same angle.
Sailors used to (and sometimes still) navigate along rhumb lines since it is easier to follow a
constant compass bearing than to be continually adjusting the bearing, as is needed to follow a
great circle. Rhumb lines are straight lines on a Mercator Projection map (also helpful for
navigation).
Rhumb lines are generally longer than great-circle (orthodrome) routes. For instance, London to
New York is 4% longer along a rhumb line than along a great circle important for aviation fuel,
but not particularly to sailing vessels. New York to Beijing close to the most extreme example
possible (though not sailable!) is 30% longer along a rhumb line.
Rhumb-line distance between two points
,
Point 1:
,
Point 2:
Distance:
5198 km
Bearing:
2600738
Midpoint:
462132N, 0384900W
view map
Destination point along rhumb line given distance and bearing from start point
,
Start point:
Bearing:
Distance:
km
Destination point:
505748N, 0015109E
view map
Key to calculations of rhumb lines is the inverse Gudermannian function, which gives the height
on a Mercator projection map of a given latitude: ln(tan + sec) or ln( tan(/4+/2) ).
This of course tends to infinity at the poles (in keeping with the Mercator projection). For
obsessives, there is even an ellipsoidal version, the isometric latitude: =
ln( tan(/4+/2) / [ (1esin) / (1+esin) ] ), or its better-conditioned equivalent =
atanh(sin) eatanh(esin).
e/2
The formul to derive Mercator projection easting and northing coordinates from spherical
latitude and longitude are then
E=R
N = R ln( tan(/4 + /2) )
The following formul are from Ed Williams aviation formulary.
Distance
Since a rhumb line is a straight line on a Mercator projection, the distance between two points
along a rhumb line is the length of that line (by Pythagoras); but the distortion of the projection
needs to be compensated for.
On a constant latitude course (travelling east-west), this compensation is simply cos; in the
general case, it is / where = ln( tan(/4 + /2) / tan(/4 + /2) ) (the
projected latitude difference)
2
Formula:
d = ( + q) R
(projected latitude
(P
where is latitude, is longitude, is taking shortest route (<180), R is the earths radius, ln is natural
var = Math.log(Math.tan(Math.PI/4+2/2)/Math.tan(Math.PI/4+1/2));
var q = Math.abs() > 10e-12 ? / : Math.cos(1); // E-W course becomes ill-conditioned with 0
JavaScript
:
(all angles
in radians)
Bearing
A rhumb line is a straight line on a Mercator projection, with an angle on the projection equal to
the compass bearing.
Formula:
(projected latitude
where is latitude, is longitude, is taking shortest route (<180), R is the earths radius, ln is natural
var = Math.log(Math.tan(Math.PI/4+2/2)/Math.tan(Math.PI/4+1/2));
var q = Math.abs() > 10e-12 ? / : Math.cos(1); // E-W course becomes ill-conditioned with 0
JavaScript
:
(all angles
in radians)
Destination
Given a start point and a distance d along constant bearing , this will calculate the destination
point. If you maintain a constant bearing along a rhumb line, you will gradually spiral in towards
one of the poles.
Formula:
= d/R
= ln( tan(/4 + /2) / tan(/4 + /2) )
q = / (or cos for E-W line)
= sin / q
2
= + cos
2
(angul
(projected latitude
= +
2
where is latitude, is longitude, is taking shortest route (<180), ln is natural log, R is the earths rad
var
var
var
var
var
= *Math.cos();
2 = 1 + ;
= Math.log(Math.tan(2/2+Math.PI/4)/Math.tan(1/2+Math.PI/4));
q = > 10e-12 ? / : Math.cos(1); // E-W course becomes ill-conditioned with 0/0
= *Math.sin()/q;
JavaScript
:
// check for some daft bugger going past the pole, normalise latitude if so
if (Math.abs(2) > Math.PI/2) 2 = 2>0 ? Math.PI-2 : -Math.PI-2;
(all angles
in radians)
2 = (1+dLon+Math.PI)%(2*Math.PI) - Math.PI;
Mid-point
This formula for calculating the loxodromic midpoint, the point half-way along a rhumb line
between two points, is due to Robert Hill and Clive Tooth (thx Axel!).
1
Formula:
= ( + ) / 2
f = tan(/4 + /2)
f = tan(/4 + /2)
f = tan(/4+ /2)
= [ ( ) ln(f ) + ln(f ) ln(f ) ] / ln(f /f )
m
JavaScript
:
(all angles
in radians)
<form>
Lat1: <input type="text" name="lat1" id="lat1"> Lon1: <input type="text" name="lon1"
id="lon1">
Lat2: <input type="text" name="lat2" id="lat2"> Lon2: <input type="text" name="lon2"
id="lon2">
<button onClick="var p1 = LatLon(Dms.parseDMS(f.lat1.value),
Dms.parseDMS(f.lon1.value));
var p2 = LatLon(Dms.parseDMS(f.lat2.value), Dms.parseDMS(f.lon2.value));
alert(p1.distanceTo(p2));">Calculate distance</button>
</form>
If you use jQuery, the code can be separated from the HTML:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="latlon.js">/* Latitude/Longitude formulae */</script>
<script src="dms.js">/* Geodesy representation conversions */</script>
<script>
$(document).ready(function() {
$('#calc-dist').click(function() {
var p1 = LatLon(Dms.parseDMS($('#lat1').val()), Dms.parseDMS($('#lon1').val()));
var p2 = LatLon(Dms.parseDMS($('#lat2').val()), Dms.parseDMS($('#lon2').val()));
$('#result-distance').html(p1.distanceTo(p2));
});
});
</script>
...
<form>
Lat1: <input type="text" name="lat1" id="lat1"> Lon1: <input type="text" name="lon1"
id="lon1">
Lat2: <input type="text" name="lat2" id="lat2"> Lon2: <input type="text" name="lon2"
id="lon2">
<button id="calc-dist">Calculate distance</button>
<output id="result-distance"></output> km
</form>
Longitude
1 111 km (110.57 eql 111.70 polar)
1 1.85 km (= 1 nm)
0.01 1.11 km
1 30.9 m
0.0001 11.1 m
degrees
deg/min
deg/min/sec
Notes:
Accuracy: since the earth is not quite a sphere, there are small errors in using spherical geometry;
the earth is actually roughly ellipsoidal (or more precisely, oblate spheroidal) with a radius
varying between about 6,378km (equatorial) and 6,357km (polar), and local radius of curvature
varying from 6,336km (equatorial meridian) to 6,399km (polar). 6,371 km is the generally
accepted value for the earths mean radius. This means that errors from assuming spherical
geometry might be up to 0.55% crossing the equator, though generally below 0.3%, depending on
latitude and direction of travel. An accuracy of better than 3m in 1km is mostly good enough for
me, but if you want greater accuracy, you could use the Vincenty formula for calculating geodesic
distances on ellipsoids, which gives results accurate to within 1mm. (Out of sheer perversity
Ive never needed such accuracy I looked up this formula and discovered the JavaScript
implementation was simpler than I expected).
Trig functions take arguments in radians, so latitude, longitude, and bearings in degrees (either
decimal or degrees/minutes/seconds) need to be converted to radians, rad = .deg/180. When
converting radians back to degrees (deg = 180.rad/), West is negative if using signed decimal
degrees. For bearings, values in the range - to + [-180 to +180] need to be converted to 0 to
+2 [0360]; this can be done by (brng+2.)%2. [or brng+360)%360] where % is the (floating
point) modulo operator.
The atan2() function widely used here takes two arguments, atan2(y, x), and computes the arc
tangent of the ratio y/x. It is more flexible than atan(y/x), since it handles x=0, and it also returns
values in all 4 quadrants - to + (the atan function returns values in the range -/2 to +/2).
All bearings are with respect to true north, 0=N, 90=E, etc; if you are working from a
compass, magnetic north varies from true north in a complex way around the earth, and the
difference has to be compensated for by variances indicated on local maps.
If you implement any formula involving atan2 in Microsoft Excel, you will need to reverse the
arguments, as Excelhas them the opposite way around from JavaScript conventional order is
atan2(y, x), but Excel uses atan2(x, y). To use atan2 in a (VBA) macro, you can use
WorksheetFunction.Atan2().
If you are using Google Maps, several of these functions are now provided in the Google Maps
API V3 spherical library (computeDistanceBetween(), computeHeading(), computeOffset(),
interpolate(), etc; note they use a default Earth radius of 6,378,137 meters).
If you use Ordnance Survey Grid References, I have implemented a script for converting between
Lat/Long & OS Grid References.
I learned a lot from the US Census Bureau GIS FAQ which is no longer available, so Ive made a
copy.
See below for the JavaScript source code, also available on GitHub. Note I use Greek letters in
variables representing maths symbols conventionally presented as Greek letters: I value the great
benefit in legibility over the minor inconvenience in typing.
With its untyped C-style syntax, JavaScript reads remarkably close to pseudo-code: exposing the
algorithms with a minimum of syntactic distractions. These functions should be simple to
translate into other languages if required, though can also be used as-is in browsers and Node.js.
I have extended base JavaScript object prototypes with trim(), toRadians(), and toDegrees()
methods: I dont see great likelihood of conflicts; trim() is now in the language, and
degree/radian conversion is ubiquitous.
I also have a page illustrating the use of the spherical law of cosines for selecting points from a
database within a specified bounding circle the example is based on MySQL+PDO, but should
be extensible to other DBMS platforms.
Several people have asked about example Excel spreadsheets, so I have implemented
the distance & bearing and thedestination point formul as spreadsheets, in a form which breaks
down the all stages involved to illustrate the operation.
January 2010: I have revised the scripts to be structured as methods of a LatLon object. Of
course, while JavaScript is object-oriented, it is a prototype-based rather than classbased language, so this is not actually a class, but isolating code into a separate namespace is
good
JavaScript
practice.
If
youre
not
familiar
with
JavaScript
syntax, LatLon.prototype.distanceTo = function(point) { ... } , for instance, defines a
distanceTo method of the LatLon object (/class) which takes a LatLonobject as a parameter
(and returns a number). The Dms namespace acts as a static class for geodesy formatting /
parsing / conversion functions.
January 2015: I have refactored the scripts to inter-operate better, and rationalised certain
aspects: the JavaScript file is now latlon-spherical.js instead of simply latlon.js; distances are
now always in metres; the earths radius is now a parameter to distance calculation methods
rather than to the constructor; the previous Geo object is now Dms, to better indicate its
purpose; the destinationPoint function has the distance parameter before the bearing.
I offer these scripts for free use and adaptation to balance my debt to the opensource info-verse. You are welcome to re-use these scripts [under an MIT licence, without any
warranty express or implied] provided solely that you retain my copyright notice and a link to
this page.
If you would like to show your appreciation and support continued development of
/*
/*
/*
/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Latitude/longitude spherical geodesy formulae & scripts
(c) Chris Veness 2002-2015 */
- www.movable-type.co.uk/scripts/latlong.html
MIT Licence */
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
if (typeof module!='undefined' && module.exports) var Dms = require('./dms'); // CommonJS (Node)
/**
* Creates a LatLon point on the earth's surface at the specified latitude / longitude.
*
* @classdesc Tools for geodetic calculations
* @requires Dms from 'dms.js'
*
* @constructor
* @param {number} lat - Latitude in degrees.
* @param {number} lon - Longitude in degrees.
*
* @example
*
var p1 = new LatLon(52.205, 0.119);
*/
function LatLon(lat, lon) {
// allow instantiation without 'new'
if (!(this instanceof LatLon)) return new LatLon(lat, lon);
this.lat = Number(lat);
this.lon = Number(lon);
/**
* Returns the distance from 'this' point to destination point (using haversine formula).
*
* @param {LatLon} point - Latitude/longitude of destination point.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {number} Distance between this point and destination point, in same units as radius.
*
* @example
*
var p1 = new LatLon(52.205, 0.119), p2 = new LatLon(48.857, 2.351);
*
var d = p1.distanceTo(p2); // Number(d.toPrecision(4)): 404300
*/
LatLon.prototype.distanceTo = function(point, radius) {
if (!(point instanceof LatLon)) throw new TypeError('point is not LatLon object');
radius = (radius === undefined) ? 6371e3 : Number(radius);
var
var
var
var
var
R = radius;
1 = this.lat.toRadians(), 1 = this.lon.toRadians();
2 = point.lat.toRadians(), 2 = point.lon.toRadians();
= 2 - 1;
= 2 - 1;
return d;
/**
* Returns the (initial) bearing from 'this' point to destination point.
*
* @param {LatLon} point - Latitude/longitude of destination point.
* @returns {number} Initial bearing in degrees from north.
*
* @example
*
var p1 = new LatLon(52.205, 0.119), p2 = new LatLon(48.857, 2.351);
*
var b1 = p1.bearingTo(p2); // b1.toFixed(1): 156.2
*/
LatLon.prototype.bearingTo = function(point) {
if (!(point instanceof LatLon)) throw new TypeError('point is not LatLon object');
var 1 = this.lat.toRadians(), 2 = point.lat.toRadians();
var = (point.lon-this.lon).toRadians();
// see http://mathforum.org/library/drmath/view/55417.html
var y = Math.sin() * Math.cos(2);
var x = Math.cos(1)*Math.sin(2) Math.sin(1)*Math.cos(2)*Math.cos();
var = Math.atan2(y, x);
};
/**
* Returns final bearing arriving at destination destination point from 'this' point; the final bearing
* will differ from the initial bearing by varying degrees according to distance and latitude.
*
* @param {LatLon} point - Latitude/longitude of destination point.
* @returns {number} Final bearing in degrees from north.
*
* @example
*
var p1 = new LatLon(52.205, 0.119), p2 = new LatLon(48.857, 2.351);
*
var b2 = p1.finalBearingTo(p2); // b2.toFixed(1): 157.9
*/
LatLon.prototype.finalBearingTo = function(point) {
if (!(point instanceof LatLon)) throw new TypeError('point is not LatLon object');
// get initial bearing from destination point to this point & reverse it by adding 180
return ( point.bearingTo(this)+180 ) % 360;
};
/**
* Returns the midpoint between 'this' point and the supplied point.
*
/**
* Returns the destination point from 'this' point having travelled the given distance on the
* given initial bearing (bearing normally varies around path followed).
*
* @param {number} distance - Distance travelled, in same units as earth radius (default: metres).
* @param {number} bearing - Initial bearing in degrees from north.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {LatLon} Destination point.
*
* @example
*
var p1 = new LatLon(51.4778, -0.0015);
*
var p2 = p1.destinationPoint(7794, 300.7); // p2.toString(): 51.5135N, 000.0983W
*/
LatLon.prototype.destinationPoint = function(distance, bearing, radius) {
radius = (radius === undefined) ? 6371e3 : Number(radius);
// see http://williams.best.vwh.net/avform.htm#LL
var = Number(distance) / radius; // angular distance in radians
var = Number(bearing).toRadians();
var 1 = this.lat.toRadians();
var 1 = this.lon.toRadians();
var 2 = Math.asin( Math.sin(1)*Math.cos() +
Math.cos(1)*Math.sin()*Math.cos() );
var 2 = 1 + Math.atan2(Math.sin()*Math.sin()*Math.cos(1),
Math.cos()-Math.sin(1)*Math.sin(2));
2 = (2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180
};
/**
* Returns the point of intersection of two paths defined by point and bearing.
*
* @param {LatLon} p1 - First point.
* @param {number} brng1 - Initial bearing from first point.
* @param {LatLon} p2 - Second point.
* @param {number} brng2 - Initial bearing from second point.
* @returns {LatLon} Destination point (null if no unique intersection defined).
*
* @example
*
var p1 = LatLon(51.8853, 0.2545), brng1 = 108.547;
*
var p2 = LatLon(49.0034, 2.5735), brng2 = 32.435;
*
var pInt = LatLon.intersection(p1, brng1, p2, brng2); // pInt.toString(): 50.9078N, 004.5084E
*/
LatLon.intersection = function(p1, brng1, p2, brng2) {
if (!(p1 instanceof LatLon)) throw new TypeError('p1 is not LatLon object');
if (!(p2 instanceof LatLon)) throw new TypeError('p2 is not LatLon object');
// see http://williams.best.vwh.net/avform.htm#Intersection
var
var
var
var
1 = p1.lat.toRadians(), 1 = p1.lon.toRadians();
2 = p2.lat.toRadians(), 2 = p2.lon.toRadians();
13 = Number(brng1).toRadians(), 23 = Number(brng2).toRadians();
= 2-1, = 2-1;
/**
* Returns (signed) distance from this point to great circle defined by start-point and end-point.
*
* @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 to great circle (-ve if to left, +ve if to right of path).
*
* @example
* var pCurrent = new LatLon(53.2611, -0.7972);
* var p1 = new LatLon(53.3206, -1.7297), p2 = new LatLon(53.1887, 0.1334);
* var d = pCurrent.crossTrackDistanceTo(p1, p2); // Number(d.toPrecision(4)): -307.5
*/
LatLon.prototype.crossTrackDistanceTo = 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');
radius = (radius === undefined) ? 6371e3 : Number(radius);
var 13 = pathStart.distanceTo(this, radius)/radius;
var 13 = pathStart.bearingTo(this).toRadians();
var 12 = pathStart.bearingTo(pathEnd).toRadians();
var dxt = Math.asin( Math.sin(13) * Math.sin(13-12) ) * radius;
};
return dxt;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Returns the distance travelling from 'this' point to destination point along a rhumb line.
*
* @param {LatLon} point - Latitude/longitude of destination point.
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres).
* @returns {number} Distance in km between this point and destination point (same units as radius).
*
* @example
*
var p1 = new LatLon(51.127, 1.338), p2 = new LatLon(50.964, 1.853);
*
var d = p1.distanceTo(p2); // Number(d.toPrecision(4)): 40310
*/
LatLon.prototype.rhumbDistanceTo = function(point, radius) {
/**
* Returns the loxodromic midpoint (along a rhumb line) between 'this' point and second point.
*
* @param {LatLon} point - Latitude/longitude of second point.
* @returns {LatLon} Midpoint between this point and second point.
*
* @example
*
var p1 = new LatLon(51.127, 1.338), p2 = new LatLon(50.964, 1.853);
*
var p2 = p1.rhumbMidpointTo(p2); // p2.toString(): 51.0455N, 001.5957E
*/
LatLon.prototype.rhumbMidpointTo = function(point) {
if (!(point instanceof LatLon)) throw new TypeError('point is not LatLon object');
// http://mathforum.org/kb/message.jspa?messageID=148837
var 1 = this.lat.toRadians(), 1 = this.lon.toRadians();
var 2 = point.lat.toRadians(), 2 = point.lon.toRadians();
if (Math.abs(2-1) > Math.PI) 1 += 2*Math.PI; // crossing anti-meridian
var
var
var
var
var
3 = (1+2)/2;
f1 = Math.tan(Math.PI/4 + 1/2);
f2 = Math.tan(Math.PI/4 + 2/2);
f3 = Math.tan(Math.PI/4 + 3/2);
3 = ( (2-1)*Math.log(f3) + 1*Math.log(f2) - 2*Math.log(f1) ) / Math.log(f2/f1);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Returns a string representation of 'this' point, formatted as degrees, degrees+minutes, or
* degrees+minutes+seconds.
*
* @param {string} [format=dms] - Format point as 'd', 'dm', 'dms'.
* @param {number} [dp=0|2|4] - Number of decimal places to use - default 0 for dms, 2 for dm, 4 for d.
* @returns {string} Comma-separated latitude/longitude.
*/
LatLon.prototype.toString = function(format, dp) {
if (format === undefined) format = 'dms';
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Extend Number object with method to convert numeric degrees to radians */
if (Number.prototype.toRadians === undefined) {
Number.prototype.toRadians = function() { return this * Math.PI / 180; };
}
/** Extend Number object with method to convert radians to numeric (signed) degrees */
if (Number.prototype.toDegrees === undefined) {
Number.prototype.toDegrees = function() { return this * 180 / Math.PI; };
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (typeof module != 'undefined' && module.exports) module.exports = LatLon; // CommonJS (Node)
if (typeof define == 'function' && define.amd) define(['Dms'], function() { return LatLon; }); // AMD
/*
/*
/*
/*
/*
/*
/*
/*
/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Geodesy representation conversion functions
(c) Chris Veness 2002-2015 */
- www.movable-type.co.uk/scripts/latlong.html
MIT Licence */
*/
Sample usage:
*/
var lat = Dms.parseDMS('51 28 40.12 N');
*/
var lon = Dms.parseDMS('000 00 05.31 W');
*/
var p1 = new LatLon(lat, lon);
*/
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
/**
* Tools for converting between numeric degrees and degrees / minutes / seconds.
*
* @namespace
*/
var Dms = {};
// note Unicode Degree = U+00B0. Prime = U+2032, Double prime = U+2033
/**
* Parses string representing degrees/minutes/seconds into numeric degrees.
*
* This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
* suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3 37 09W).
* Seconds and minutes may be omitted.
*
* @param {string|number} dmsStr - Degrees or deg/min/sec in variety of formats.
* @returns {number} Degrees as decimal number.
*/
Dms.parseDMS = function(dmsStr) {
// check for signed decimal degrees without NSEW, if so return it directly
if (typeof dmsStr == 'number' && isFinite(dmsStr)) return Number(dmsStr);
// strip off any sign or compass dir'n & split out separate d/m/s
var dms = String(dmsStr).trim().replace(/^-/, '').replace(/[NSEW]$/i, '').split(/[^0-9.,]+/);
if (dms[dms.length-1]=='') dms.splice(dms.length-1); // from trailing symbol
if (dms == '') return NaN;
// and convert to decimal degrees...
var deg;
switch (dms.length) {
case 3: // interpret 3-part result as d/m/s
deg = dms[0]/1 + dms[1]/60 + dms[2]/3600;
break;
case 2: // interpret 2-part result as d/m
deg = dms[0]/1 + dms[1]/60;
break;
case 1: // just d (possibly decimal) or non-separated dddmmss
deg = dms[0];
// check for fixed-width unseparated format eg 0033709W
//if (/[NS]/i.test(dmsStr)) deg = '0' + deg; // - normalise N/S to 3-digit degrees
//if (/[0-9]{7}/.test(deg)) deg = deg.slice(0,3)/1 + deg.slice(3,5)/60 + deg.slice(5)/3600;
break;
default:
return NaN;
}
if (/^-|[WS]$/i.test(dmsStr.trim())) deg = -deg; // take '-', west and south as -ve
return Number(deg);
};
/**
};
/**
* Converts numeric degrees to deg/min/sec latitude (2-digit degrees, suffixed with N/S).
*
* @param {number} deg - Degrees to be formatted as specified.
* @param {string} [format=dms] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec.
* @param {number} [dp=0|2|4] - Number of decimal places to use default 0 for dms, 2 for dm, 4 for d.
* @returns {string} Degrees formatted as deg/min/secs according to specified format.
*/
Dms.toLat = function(deg, format, dp) {
var lat = Dms.toDMS(deg, format, dp);
return lat===null ? '' : lat.slice(1) + (deg<0 ? 'S' : 'N'); // knock off initial '0' for lat!
};
/**
* Convert numeric degrees to deg/min/sec longitude (3-digit degrees, suffixed with E/W)
*
* @param {number} deg - Degrees to be formatted as specified.
* @param {string} [format=dms] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec.
* @param {number} [dp=0|2|4] - Number of decimal places to use default 0 for dms, 2 for dm, 4 for d.
* @returns {string} Degrees formatted as deg/min/secs according to specified format.
*/
Dms.toLon = function(deg, format, dp) {
var lon = Dms.toDMS(deg, format, dp);
return lon===null ? '' : lon + (deg<0 ? 'W' : 'E');
};
/**
* Converts numeric degrees to deg/min/sec as a bearing (0..360)
*
* @param {number} deg - Degrees to be formatted as specified.
* @param {string} [format=dms] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec.
* @param {number} [dp=0|2|4] - Number of decimal places to use default 0 for dms, 2 for dm, 4 for d.
* @returns {string} Degrees formatted as deg/min/secs according to specified format.
*/
Dms.toBrng = function(deg, format, dp) {
deg = (Number(deg)+360) % 360; // normalise -ve values to 180..360
var brng = Dms.toDMS(deg, format, dp);
return brng===null ? '' : brng.replace('360', '0'); // just in case rounding took us up to 360!
};
/**
* Returns compass point (to given precision) for supplied bearing.
*
* @param {number} bearing - Bearing in degrees from north.
* @param {number} [precision=3] - Precision (cardinal / intercardinal / secondary-intercardinal).
* @returns {string} Compass point for supplied bearing.
*
* @example
* var point = Dms.compassPoint(24); // point = 'NNE'
* var point = Dms.compassPoint(24, 1); // point = 'N'
*/
Dms.compassPoint = function(bearing, precision) {