Skip to content

Commit 73f5fe9

Browse files
committed
v2.0.0
1 parent f84c319 commit 73f5fe9

39 files changed

+6969
-3904
lines changed

.travis.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
language: node_js
2+
23
node_js:
3-
- "node"
4-
- "4"
4+
- "lts/*"
5+
- "node"
6+
- "8"
7+
8+
os:
9+
- linux
10+
- osx
11+
- windows
12+
13+
after_success:
14+
- nyc npm test && nyc report --reporter=text-lcov | coveralls

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
## [2.0.0] - 2019-02-14
4+
5+
### Changed
6+
7+
- Restructured to use ES modules, ES2015 syntax
8+
- Separated n-vector functions into spherical / ellipsoidal
9+
- General rationalisation of API
10+
11+
### Added
12+
13+
- Modern terrestrial reference frames (TRFs) to complement historical datums
14+
- LatLon.parse() methods
15+
- latlon.toString() numeric format ‘n’
16+
17+
### Breaking
18+
19+
- LatLon is now a class, so the new operator is no longer optional on the constructor
20+
- latlon.bearingTo() is now latlon.initialBearingTo()
21+
- latlon.toString() defaults to ‘d’ in place of ‘dms’
22+
- LatLon.ellipse, LatLon.datum are now LatLon.ellipses, LatLon.datums
23+
- Dms.parseDMS() is now simply Dms.parse()
24+
- Dms.toDMS() is now Dms.toDms()
25+
- Dms.defaultSeparator (between degree, minute, second values) defaults to ‘narrow no-break space’ in place of no space

README.md

Lines changed: 145 additions & 388 deletions
Large diffs are not rendered by default.

dms.js

Lines changed: 287 additions & 180 deletions
Large diffs are not rendered by default.

latlon-ellipsoidal-datum.js

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2+
/* Geodesy tools for conversions between (historical) datums (c) Chris Veness 2005-2019 */
3+
/* MIT Licence */
4+
/* www.movable-type.co.uk/scripts/latlong-convert-coords.html */
5+
/* www.movable-type.co.uk/scripts/geodesy-library.html#latlon-ellipsoidal-datum */
6+
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
7+
8+
9+
import LatLonEllipsoidal, { Cartesian, Dms } from './latlon-ellipsoidal.js';
10+
11+
12+
/**
13+
* Historical geodetic datums: a latitude/longitude point defines a geographic location on or
14+
* above/below the earth’s surface, measured in degrees from the equator & the International
15+
* Reference Meridian and metres above the ellipsoid, and based on a given datum. The datum is
16+
* based on a reference ellipsoid and tied to geodetic survey reference points.
17+
*
18+
* Modern geodesy is generally based on the WGS84 datum (as used for instance by GPS systems), but
19+
* previously various reference ellipsoids and datum references were used.
20+
*
21+
* This module extends the core latlon-ellipsoidal module to include ellipsoid parameters and datum
22+
* transformation parameters, and methods for converting between different (generally historical)
23+
* datums.
24+
*
25+
* It can be used for UK Ordnance Survey mapping (OS National Grid References are still based on the
26+
* otherwise historical OSGB36 datum), as well as for historical purposes.
27+
*
28+
* q.v. Ordnance Survey ‘A guide to coordinate systems in Great Britain’ Section 6,
29+
* www.ordnancesurvey.co.uk/docs/support/guide-coordinate-systems-great-britain.pdf, and also
30+
* www.ordnancesurvey.co.uk/blog/2014/12/2.
31+
*
32+
* @module latlon-ellipsoidal-datum
33+
*/
34+
35+
36+
/*
37+
* Ellipsoid parameters; exposed through static getter below.
38+
*/
39+
const ellipsoids = {
40+
WGS84: { a: 6378137, b: 6356752.314245, f: 1/298.257223563 },
41+
Airy1830: { a: 6377563.396, b: 6356256.909, f: 1/299.3249646 },
42+
AiryModified: { a: 6377340.189, b: 6356034.448, f: 1/299.3249646 },
43+
Bessel1841: { a: 6377397.155, b: 6356078.962818, f: 1/299.1528128 },
44+
Clarke1866: { a: 6378206.4, b: 6356583.8, f: 1/294.978698214 },
45+
Clarke1880IGN: { a: 6378249.2, b: 6356515.0, f: 1/293.466021294 },
46+
GRS80: { a: 6378137, b: 6356752.314140, f: 1/298.257222101 },
47+
Intl1924: { a: 6378388, b: 6356911.946, f: 1/297 }, // aka Hayford
48+
WGS72: { a: 6378135, b: 6356750.5, f: 1/298.26 },
49+
};
50+
51+
52+
/*
53+
* Datums; exposed through static getter below.
54+
*/
55+
const datums = {
56+
// transforms: t in metres, s in ppm, r in arcseconds tx ty tz s rx ry rz
57+
ED50: { ellipsoid: ellipsoids.Intl1924, transform: [ 89.5, 93.8, 123.1, -1.2, 0.0, 0.0, 0.156 ] }, // epsg.io/1311
58+
// en.wikipedia.org/wiki/European_Terrestrial_Reference_System_1989
59+
Irl1975: { ellipsoid: ellipsoids.AiryModified, transform: [ -482.530, 130.596, -564.557, -8.150, 1.042, 0.214, 0.631 ] }, // epsg.io/1954
60+
NAD27: { ellipsoid: ellipsoids.Clarke1866, transform: [ 8, -160, -176, 0, 0, 0, 0 ] },
61+
NAD83: { ellipsoid: ellipsoids.GRS80, transform: [ 0.9956, -1.9103, -0.5215, -0.00062, 0.025915, 0.009426, 0.011599 ] },
62+
NTF: { ellipsoid: ellipsoids.Clarke1880IGN, transform: [ 168, 60, -320, 0, 0, 0, 0 ] },
63+
OSGB36: { ellipsoid: ellipsoids.Airy1830, transform: [ -446.448, 125.157, -542.060, 20.4894, -0.1502, -0.2470, -0.8421 ] }, // epsg.io/1314
64+
Potsdam: { ellipsoid: ellipsoids.Bessel1841, transform: [ -582, -105, -414, -8.3, 1.04, 0.35, -3.08 ] },
65+
TokyoJapan: { ellipsoid: ellipsoids.Bessel1841, transform: [ 148, -507, -685, 0, 0, 0, 0 ] },
66+
WGS72: { ellipsoid: ellipsoids.WGS72, transform: [ 0, 0, -4.5, -0.22, 0, 0, 0.554 ] },
67+
WGS84: { ellipsoid: ellipsoids.WGS84, transform: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] },
68+
};
69+
/* sources:
70+
* - ED50: www.gov.uk/guidance/oil-and-gas-petroleum-operations-notices#pon-4
71+
* - Irl1975: www.osi.ie/wp-content/uploads/2015/05/transformations_booklet.pdf
72+
* - NAD27: en.wikipedia.org/wiki/Helmert_transformation
73+
* - NAD83: www.uvm.edu/giv/resources/WGS84_NAD83.pdf [strictly, WGS84(G1150) -> NAD83(CORS96) @ epoch 1997.0]
74+
* (note NAD83(1986) ≡ WGS84(Original); confluence.qps.nl/pages/viewpage.action?pageId=29855173)
75+
* - NTF: Nouvelle Triangulation Francaise geodesie.ign.fr/contenu/fichiers/Changement_systeme_geodesique.pdf
76+
* - OSGB36: www.ordnancesurvey.co.uk/docs/support/guide-coordinate-systems-great-britain.pdf
77+
* - Potsdam: kartoweb.itc.nl/geometrics/Coordinate%20transformations/coordtrans.html
78+
* - TokyoJapan: www.geocachingtoolbox.com?page=datumEllipsoidDetails
79+
* - WGS72: www.icao.int/safety/pbn/documentation/eurocontrol/eurocontrol wgs 84 implementation manual.pdf
80+
*
81+
* more transform parameters are available from earth-info.nga.mil/GandG/coordsys/datums/NATO_DT.pdf,
82+
* www.fieldenmaps.info/cconv/web/cconv_params.js
83+
*/
84+
/* note:
85+
* - ETRS89 reference frames are coincident with WGS-84 at epoch 1989.0 (ie null transform) at the one metre level.
86+
*/
87+
88+
89+
// freeze static properties
90+
Object.keys(ellipsoids).forEach(e => Object.freeze(ellipsoids[e]));
91+
Object.keys(datums).forEach(d => { Object.freeze(datums[d]); Object.freeze(datums[d].transform); });
92+
93+
94+
/* LatLon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
95+
96+
97+
/**
98+
* Latitude/longitude points on an ellipsoidal model earth, with ellipsoid parameters and methods
99+
* for converting between datums and to geocentric (ECEF) cartesian coordinates.
100+
*
101+
* @extends LatLonEllipsoidal
102+
*/
103+
class LatLonEllipsoidal_Datum extends LatLonEllipsoidal {
104+
105+
/**
106+
* Creates a geodetic latitude/longitude point on an ellipsoidal model earth using given datum.
107+
*
108+
* @param {number} lat - Latitude (in degrees).
109+
* @param {number} lon - Longitude (in degrees).
110+
* @param {number} [height=0] - Height above ellipsoid in metres.
111+
* @param {LatLon.datums} datum - Datum this point is defined within.
112+
*
113+
* @example
114+
* import LatLon from '/js/geodesy/latlon-ellipsoidal-datum.js';
115+
* const p = new LatLon(53.3444, -6.2577, 17, LatLon.datums.Irl1975);
116+
*/
117+
constructor(lat, lon, height=0, datum=datums.WGS84) {
118+
if (!datum || datum.transform==undefined) throw new TypeError(`Unrecognised datum ‘${datum}’`);
119+
120+
super(lat, lon, height);
121+
122+
this._datum = datum;
123+
}
124+
125+
126+
/**
127+
* Datum this point is defined within.
128+
*/
129+
get datum() {
130+
return this._datum;
131+
}
132+
133+
134+
/**
135+
* Ellipsoids with their parameters; semi-major axis (a), semi-minor axis (b), and flattening (f).
136+
*
137+
* Flattening f = (a−b)/a; at least one of these parameters is derived from defining constants.
138+
*
139+
* @example
140+
* const a = LatLon.ellipsoids.Airy1830.a; // 6377563.396
141+
*/
142+
static get ellipsoids() {
143+
return ellipsoids;
144+
}
145+
146+
147+
/**
148+
* Datums; with associated ellipsoid, and Helmert transform parameters to convert from WGS-84
149+
* into given datum.
150+
*
151+
* Note that precision of various datums will vary, and WGS-84 (original) is not defined to be
152+
* accurate to better than ±1 metre. No transformation should be assumed to be accurate to
153+
* better than a metre, for many datums somewhat less.
154+
*
155+
* This is a small sample of commoner datums from a large set of historical datums. I will add
156+
* new datums on request.
157+
*
158+
* @example
159+
* const a = LatLon.datums.OSGB36.ellipsoid.a; // 6377563.396
160+
* const tx = LatLon.datums.OSGB36.transform; // [ tx, ty, tz, s, rx, ry, rz ]
161+
* const availableDatums = Object.keys(LatLon.datums).join(', '); // ED50, Irl1975, NAD27, ...
162+
*/
163+
static get datums() {
164+
return datums;
165+
}
166+
167+
168+
// note instance datum getter/setters are in LatLonEllipsoidal
169+
170+
171+
/**
172+
* Parses a latitude/longitude point from a variety of formats.
173+
*
174+
* Latitude & longitude (in degrees) can be supplied as two separate parameters, as a single
175+
* comma-separated lat/lon string, or as a single object with { lat, lon } or GeoJSON properties.
176+
*
177+
* The latitude/longitude values may be numeric or strings; they may be signed decimal or
178+
* deg-min-sec (hexagesimal) suffixed by compass direction (NSEW); a variety of separators are
179+
* accepted. Examples -3.62, '3 37 12W', '3°37′12″W'.
180+
*
181+
* Thousands/decimal separators must be comma/dot; use Dms.fromLocale to convert locale-specific
182+
* thousands/decimal separators.
183+
*
184+
* @param {number|string|Object} lat|latlon - Geodetic Latitude (in degrees) or comma-separated lat/lon or lat/lon object.
185+
* @param {number} [lon] - Longitude in degrees.
186+
* @param {number} [height=0] - Height above ellipsoid in metres.
187+
* @param {LatLon.datums} [datum=LatLon.datums.WGS84] - Datum this point is defined within.
188+
* @returns {LatLon} Latitude/longitude point on ellipsoidal model earth using given datum.
189+
* @throws {TypeError} Unrecognised datum.
190+
*
191+
* @example
192+
* const p = LatLon.parse('51.47736, 0.0000', 0, LatLon.datums.OSGB36);
193+
*/
194+
static parse(...args) {
195+
let datum = datums.WGS84;
196+
197+
// if the last argument is a datum, use that, otherwise use default WGS-84
198+
if (args.length==4 || (args.length==3 && typeof args[2] == 'object')) datum = args.pop();
199+
200+
if (!datum || datum.transform==undefined) throw new TypeError(`Unrecognised datum ‘${datum}’`);
201+
202+
const point = super.parse(...args);
203+
204+
point._datum = datum;
205+
206+
return point;
207+
}
208+
209+
210+
/**
211+
* Converts ‘this’ lat/lon coordinate to new coordinate system.
212+
*
213+
* @param {LatLon.datums} toDatum - Datum this coordinate is to be converted to.
214+
* @returns {LatLon} This point converted to new datum.
215+
* @throws {TypeError} Unrecognised datum.
216+
*
217+
* @example
218+
* const pWGS84 = new LatLon(51.47788, -0.00147, 0, LatLon.datums.WGS84);
219+
* const pOSGB = pWGS84.convertDatum(LatLon.datums.OSGB36); // 51.4773°N, 000.0001°E
220+
*/
221+
convertDatum(toDatum) {
222+
if (toDatum == undefined || toDatum.transform == undefined) throw new TypeError('Unrecognised datum');
223+
224+
let oldLatLon = this;
225+
let transform = null;
226+
227+
if (oldLatLon.datum == datums.WGS84) {
228+
// converting from WGS 84
229+
transform = toDatum.transform;
230+
}
231+
if (toDatum == datums.WGS84) {
232+
// converting to WGS 84; use inverse transform
233+
transform = oldLatLon.datum.transform.map(p => -p);
234+
}
235+
if (transform == null) {
236+
// neither this.datum nor toDatum are WGS84: convert this to WGS84 first
237+
oldLatLon = this.convertDatum(datums.WGS84);
238+
transform = toDatum.transform;
239+
}
240+
241+
const oldCartesian = oldLatLon.toCartesian(); // convert geodetic to cartesian...
242+
const newCartesian = oldCartesian.applyTransform(transform); // ...apply transform...
243+
const newLatLon = newCartesian.toLatLon(toDatum); // ...and convert cartesian to geodetic
244+
245+
return newLatLon;
246+
}
247+
248+
249+
/**
250+
* Converts ‘this’ point from (geodetic) latitude/longitude coordinates to (geocentric) cartesian
251+
* (x/y/z) coordinates.
252+
*
253+
* @returns {Cartesian} Cartesian point equivalent to lat/lon point, with x, y, z in metres from
254+
* earth centre.
255+
*/
256+
toCartesian() {
257+
const cartesian = super.toCartesian();
258+
const cartesianDatums = new Cartesian_Datum(cartesian.x, cartesian.y, cartesian.z);
259+
return cartesianDatums;
260+
}
261+
262+
}
263+
264+
265+
/* Cartesian - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
266+
267+
268+
/**
269+
* Converts geocentric ECEF (earth-centered earth-fixed) cartesian coordinates to latitude/longitude points,
270+
* applies Helmert transformations.
271+
*
272+
* @extends Cartesian
273+
*/
274+
class Cartesian_Datum extends Cartesian {
275+
276+
/**
277+
* Converts ‘this’ (geocentric) cartesian (x/y/z) coordinate to (geodetic) latitude/longitude
278+
* point on specified datum.
279+
*
280+
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with LatLonEllipsoidal_Datum methods
281+
* convertDatum, toCartesian, etc.
282+
*
283+
* @param {LatLon.datums} [datum=LatLon.datums.WGS84] - Datum to use when converting point.
284+
* @returns {LatLon} Latitude/longitude point defined by cartesian coordinates, in given datum.
285+
*
286+
* @example
287+
* const c = new Cartesian(4027893.924, 307041.993, 4919474.294)
288+
* const p = c.toLatLon().convertDatum(LatLon.datums.OSGB36); // 50.7971°N, 004.3612°E
289+
*/
290+
toLatLon(datum=datums.WGS84) {
291+
if (!datum) throw new TypeError('Unrecognised datum');
292+
const latLon = super.toLatLon(datum.ellipsoid);
293+
return new LatLonEllipsoidal_Datum(latLon.lat, latLon.lon, latLon.height, datum);
294+
}
295+
296+
297+
/**
298+
* Applies Helmert 7-parameter transformation to ‘this’ coordinate using transform parameters t.
299+
*
300+
* This is used in converting datums (geodetic->cartesian, apply transform, cartesian->geodetic).
301+
*
302+
* @private
303+
* @param {number[]} t - Transformation to apply to this coordinate.
304+
* @returns {Cartesian} Transformed point.
305+
*/
306+
applyTransform(t) {
307+
// this point
308+
const x1 = this.x, y1 = this.y, z1 = this.z;
309+
310+
// transform parameters
311+
const tx = t[0]; // x-shift in metres
312+
const ty = t[1]; // y-shift in metres
313+
const tz = t[2]; // z-shift in metres
314+
const s = t[3]/1e6 + 1; // scale: normalise parts-per-million to (s+1)
315+
const rx = (t[4]/3600).toRadians(); // x-rotation: normalise arcseconds to radians
316+
const ry = (t[5]/3600).toRadians(); // y-rotation: normalise arcseconds to radians
317+
const rz = (t[6]/3600).toRadians(); // z-rotation: normalise arcseconds to radians
318+
319+
// apply transform
320+
const x2 = tx + x1*s - y1*rz + z1*ry;
321+
const y2 = ty + x1*rz + y1*s - z1*rx;
322+
const z2 = tz - x1*ry + y1*rx + z1*s;
323+
324+
return new Cartesian_Datum(x2, y2, z2);
325+
}
326+
}
327+
328+
329+
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
330+
331+
export { LatLonEllipsoidal_Datum as default, Cartesian_Datum as Cartesian, datums, Dms };

0 commit comments

Comments
 (0)