Skip to content

Commit 71d4e0e

Browse files
committedJun 3, 2019
Harmonise Cartesian_Datum & Cartesian_ReferenceFrame
Adds Cartesian_Datum.convertDatum(), deprecates Cartesian_Datum.toLatLon() datum param. Plus some general tidy-up.
1 parent 90ea2cb commit 71d4e0e

File tree

5 files changed

+135
-64
lines changed

5 files changed

+135
-64
lines changed
 

‎latlon-ellipsoidal-datum.js

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class LatLonEllipsoidal_Datum extends LatLonEllipsoidal {
184184
* @param {number|string|Object} lat|latlon - Geodetic Latitude (in degrees) or comma-separated lat/lon or lat/lon object.
185185
* @param {number} [lon] - Longitude in degrees.
186186
* @param {number} [height=0] - Height above ellipsoid in metres.
187-
* @param {LatLon.datums} [datum=LatLon.datums.WGS84] - Datum this point is defined within.
187+
* @param {LatLon.datums} [datum=WGS84] - Datum this point is defined within.
188188
* @returns {LatLon} Latitude/longitude point on ellipsoidal model earth using given datum.
189189
* @throws {TypeError} Unrecognised datum.
190190
*
@@ -221,26 +221,9 @@ class LatLonEllipsoidal_Datum extends LatLonEllipsoidal {
221221
convertDatum(toDatum) {
222222
if (!toDatum || toDatum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${toDatum}’`);
223223

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
224+
const oldCartesian = this.toCartesian(); // convert geodetic to cartesian
225+
const newCartesian = oldCartesian.convertDatum(toDatum); // convert datum
226+
const newLatLon = newCartesian.toLatLon(); // convert cartesian back to geodetic
244227

245228
return newLatLon;
246229
}
@@ -255,7 +238,7 @@ class LatLonEllipsoidal_Datum extends LatLonEllipsoidal {
255238
*/
256239
toCartesian() {
257240
const cartesian = super.toCartesian();
258-
const cartesianDatum = new Cartesian_Datum(cartesian.x, cartesian.y, cartesian.z);
241+
const cartesianDatum = new Cartesian_Datum(cartesian.x, cartesian.y, cartesian.z, this.datum);
259242
return cartesianDatum;
260243
}
261244

@@ -266,31 +249,117 @@ class LatLonEllipsoidal_Datum extends LatLonEllipsoidal {
266249

267250

268251
/**
269-
* Converts geocentric ECEF (earth-centered earth-fixed) cartesian coordinates to latitude/longitude points,
270-
* applies Helmert transformations.
252+
* Augments Cartesian with datum the cooordinate is based on, and methods to convert between datums
253+
* (using Helmert 7-parameter transforms) and to convert cartesian to geodetic latitude/longitude
254+
* point.
271255
*
272256
* @extends Cartesian
273257
*/
274258
class Cartesian_Datum extends Cartesian {
275259

260+
/**
261+
* Creates cartesian coordinate representing ECEF (earth-centric earth-fixed) point, on a given
262+
* datum. The datum will identify the primary meridian (for the x-coordinate), and is also
263+
* useful in transforming to/from geodetic (lat/lon) coordinates.
264+
*
265+
* @param {number} x - X coordinate in metres (=> 0°N,0°E).
266+
* @param {number} y - Y coordinate in metres (=> 0°N,90°E).
267+
* @param {number} z - Z coordinate in metres (=> 90°N).
268+
* @param {LatLon.datums} [datum] - Datum this coordinate is defined within.
269+
* @throws {TypeError} Unrecognised datum.
270+
*
271+
* @example
272+
* import { Cartesian } from '/js/geodesy/latlon-ellipsoidal-datum.js';
273+
* const coord = new Cartesian(3980581.210, -111.159, 4966824.522);
274+
*/
275+
constructor(x, y, z, datum=undefined) {
276+
if (datum && datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`);
277+
278+
super(x, y, z);
279+
280+
if (datum) this._datum = datum;
281+
}
282+
283+
284+
/**
285+
* Datum this point is defined within.
286+
*/
287+
get datum() {
288+
return this._datum;
289+
}
290+
set datum(datum) {
291+
if (!datum || datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`);
292+
this._datum = datum;
293+
}
294+
295+
276296
/**
277297
* Converts ‘this’ (geocentric) cartesian (x/y/z) coordinate to (geodetic) latitude/longitude
278-
* point on specified datum.
298+
* point (based on the same datum, or WGS84 if unset).
279299
*
280-
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with LatLonEllipsoidal_Datum methods
281-
* convertDatum, toCartesian, etc.
300+
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with LatLonEllipsoidal_Datum
301+
* methods convertDatum, toCartesian, etc.
282302
*
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.
303+
* @returns {LatLon} Latitude/longitude point defined by cartesian coordinates.
304+
* @throws {TypeError} Unrecognised datum
285305
*
286306
* @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
307+
* const c = new Cartesian(4027893.924, 307041.993, 4919474.294);
308+
* const p = c.toLatLon(); // 50.7978°N, 004.3592°E
289309
*/
290-
toLatLon(datum=datums.WGS84) {
310+
toLatLon(deprecatedDatum=undefined) {
311+
if (deprecatedDatum) {
312+
console.info('datum parameter to Cartesian_Datum.toLatLon is deprecated: set datum before calling toLatLon()');
313+
this.datum = deprecatedDatum;
314+
}
315+
const datum = this.datum || datums.WGS84;
291316
if (!datum || datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`);
292-
const latLon = super.toLatLon(datum.ellipsoid);
293-
return new LatLonEllipsoidal_Datum(latLon.lat, latLon.lon, latLon.height, datum);
317+
318+
const latLon = super.toLatLon(datum.ellipsoid); // TODO: what if datum is not geocentric?
319+
const point = new LatLonEllipsoidal_Datum(latLon.lat, latLon.lon, latLon.height, this.datum);
320+
return point;
321+
}
322+
323+
324+
/**
325+
* Converts ‘this’ cartesian coordinate to new datum using Helmert 7-parameter transformation.
326+
*
327+
* @param {LatLon.datums} toDatum - Datum this coordinate is to be converted to.
328+
* @returns {Cartesian} This point converted to new datum.
329+
* @throws {Error} Undefined datum.
330+
*
331+
* @example
332+
* const c = new Cartesian(3980574.247, -102.127, 4966830.065, LatLon.datums.OSGB36);
333+
* c.convertDatum(LatLon.datums.Irl1975); // [??,??,??]
334+
*/
335+
convertDatum(toDatum) {
336+
// TODO: what if datum is not geocentric?
337+
if (!toDatum || toDatum.ellipsoid == undefined) throw new TypeError(`unrecognised datum ‘${toDatum}’`);
338+
if (!this.datum) throw new TypeError('cartesian coordinate has no datum');
339+
340+
let oldCartesian = null;
341+
let transform = null;
342+
343+
if (this.datum == undefined || this.datum == datums.WGS84) {
344+
// converting from WGS 84
345+
oldCartesian = this;
346+
transform = toDatum.transform;
347+
}
348+
if (toDatum == datums.WGS84) {
349+
// converting to WGS 84; use inverse transform
350+
oldCartesian = this;
351+
transform = this.datum.transform.map(p => -p);
352+
}
353+
if (transform == null) {
354+
// neither this.datum nor toDatum are WGS84: convert this to WGS84 first
355+
oldCartesian = this.convertDatum(datums.WGS84);
356+
transform = toDatum.transform;
357+
}
358+
359+
const newCartesian = oldCartesian.applyTransform(transform);
360+
newCartesian.datum = toDatum;
361+
362+
return newCartesian;
294363
}
295364

296365

‎latlon-ellipsoidal-referenceframe.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ class LatLonEllipsoidal_ReferenceFrame extends LatLonEllipsoidal {
267267
*/
268268
toCartesian() {
269269
const cartesian = super.toCartesian();
270-
return new Cartesian_ReferenceFrame(cartesian.x, cartesian.y, cartesian.z, this.referenceFrame, this.epoch);
270+
const cartesianReferenceFrame = new Cartesian_ReferenceFrame(cartesian.x, cartesian.y, cartesian.z, this.referenceFrame, this.epoch);
271+
return cartesianReferenceFrame;
271272
}
272273

273274

@@ -305,16 +306,17 @@ class LatLonEllipsoidal_ReferenceFrame extends LatLonEllipsoidal {
305306

306307
/**
307308
* Augments Cartesian with reference frame and observation epoch the cooordinate is based on, and
308-
* methods to convert between reference frames (using Helmert 14-parameter transforms), and to
309-
* convert to geodetic latitude/longitude points.
309+
* methods to convert between reference frames (using Helmert 14-parameter transforms) and to
310+
* convert cartesian to geodetic latitude/longitude point.
310311
*
311312
* @extends Cartesian
312313
*/
313314
class Cartesian_ReferenceFrame extends Cartesian {
314315

315316
/**
316-
* Creates cartesian coordinate representing ECEF (earth-centric earth-fixed) point on a given
317-
* reference frame.
317+
* Creates cartesian coordinate representing ECEF (earth-centric earth-fixed) point, on a given
318+
* reference frame. The reference frame will identify the primary meridian (for the x-coordinate),
319+
* and is also useful in transforming to/from geodetic (lat/lon) coordinates.
318320
*
319321
* @param {number} x - X coordinate in metres (=> 0°N,0°E).
320322
* @param {number} y - Y coordinate in metres (=> 0°N,90°E).
@@ -324,7 +326,7 @@ class Cartesian_ReferenceFrame extends Cartesian {
324326
* @throws {TypeError} Unrecognised reference frame, Invalid epoch.
325327
*
326328
* @example
327-
* import { Cartesian } from '/js/geodesy/latlon-ellipsoidal.js';
329+
* import { Cartesian } from '/js/geodesy/latlon-ellipsoidal-referenceframe.js';
328330
* const coord = new Cartesian(3980581.210, -111.159, 4966824.522);
329331
*/
330332
constructor(x, y, z, referenceFrame=undefined, epoch=undefined) {
@@ -363,24 +365,23 @@ class Cartesian_ReferenceFrame extends Cartesian {
363365

364366
/**
365367
* Converts ‘this’ (geocentric) cartesian (x/y/z) coordinate to (geodetic) latitude/longitude
366-
* point.
368+
* point (based on the same reference frame).
367369
*
368-
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with
369-
* LatLonEllipsoidal_ReferenceFrame methods.
370+
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with LatLonEllipsoidal_ReferenceFrame
371+
* methods convertReferenceFrame, toCartesian, etc.
370372
*
371-
* @param {LatLon.referenceFrames} [referenceFrame] - Reference frame to use when converting point.
372373
* @returns {LatLon} Latitude/longitude point defined by cartesian coordinates, in given reference frame.
373374
* @throws {Error} No reference frame defined.
374375
*
375376
* @example
376-
* const p = new Cartesian(4027893.924, 307041.993, 4919474.294, LatLon.referenceFrames.ITRF2000).toLatLon();
377+
* const c = new Cartesian(4027893.924, 307041.993, 4919474.294, LatLon.referenceFrames.ITRF2000);
378+
* const p = c.toLatLon(); // 50.7978°N, 004.3592°E
377379
*/
378380
toLatLon() {
379381
if (!this.referenceFrame) throw new Error('cartesian reference frame not defined');
380382

381383
const latLon = super.toLatLon(this.referenceFrame.ellipsoid);
382-
const point = new LatLonEllipsoidal_ReferenceFrame(latLon.lat, latLon.lon, latLon.height, this.referenceFrame);
383-
if (this.epoch) point._epoch = this.epoch;
384+
const point = new LatLonEllipsoidal_ReferenceFrame(latLon.lat, latLon.lon, latLon.height, this.referenceFrame, this.epoch);
384385
return point;
385386
}
386387

@@ -390,7 +391,7 @@ class Cartesian_ReferenceFrame extends Cartesian {
390391
* transformation. The observation epoch is unchanged.
391392
*
392393
* Note that different conversions have different tolerences; refer to the literature if
393-
* tolerances are singnificant.
394+
* tolerances are significant.
394395
*
395396
* @param {LatLon.referenceFrames} toReferenceFrame - Reference frame this coordinate is to be converted to.
396397
* @returns {Cartesian} This point converted to new reference frame.

‎latlon-ellipsoidal.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import Vector3d from './vector3d.js';
2929

3030
/*
3131
* Ellipsoid parameters; exposed through static getter below.
32+
*
33+
* The only ellipsoid defined is WGS84, for use in utm/mgrs, vincenty, nvector.
3234
*/
3335
const ellipsoids = {
3436
WGS84: { a: 6378137, b: 6356752.314245, f: 1/298.257223563 },
@@ -37,6 +39,8 @@ const ellipsoids = {
3739

3840
/*
3941
* Datums; exposed through static getter below.
42+
*
43+
* The only datum defined is WGS84, for use in utm/mgrs, vincenty, nvector.
4044
*/
4145
const datums = {
4246
WGS84: { ellipsoid: ellipsoids.WGS84 },
@@ -332,8 +336,7 @@ class LatLonEllipsoidal {
332336

333337

334338
/**
335-
* Converts ECEF (earth-centered earth-fixed) geocentric cartesian coordinates to latitude/longitude
336-
* points, applies Helmert transformations.
339+
* ECEF (earth-centered earth-fixed) geocentric cartesian coordinates.
337340
*
338341
* @extends Vector3d
339342
*/
@@ -364,19 +367,16 @@ class Cartesian extends Vector3d {
364367
*
365368
* @param {LatLon.ellipsoids} [ellipsoid=WGS84] - Ellipsoid to use when converting point.
366369
* @returns {LatLon} Latitude/longitude point defined by cartesian coordinates, on given ellipsoid.
367-
* @throws {TypeError} Invalid ellipsoid
370+
* @throws {TypeError} Invalid ellipsoid.
368371
*
369372
* @example
370-
* const p = new Cartesian(4027893.924, 307041.993, 4919474.294).toLatLon(); // 50.7978°N, 004.3592°E
371-
*
372-
* FORTHCOMING CHANGE NOTICE: Cartesian is not normally imported directly from latlon-ellipsoidal.js
373-
* (rather from latlon-ellipsoidal-datum.js), but if it is, the toLatLon() method takes an
374-
* ellipsoid as a parameter rather than a datum. This will be corrected in the next semver-major
375-
* release.
373+
* const c = new Cartesian(4027893.924, 307041.993, 4919474.294);
374+
* const p = c.toLatLon(); // 50.7978°N, 004.3592°E
376375
*/
377-
toLatLon(ellipsoid=ellipsoids.WGS84) { // TODO: make parameter datum for consistency with Cartesian_Datum.toLatLon()
378-
// note ellipsoid is available as a parameter for when toLatLon gets subclassed.
379-
if (!ellipsoid || !ellipsoid.a) throw new TypeError('invalid ellipsoid');
376+
toLatLon(ellipsoid=ellipsoids.WGS84) {
377+
// note ellipsoid is available as a parameter for when toLatLon gets subclassed to
378+
// Ellipsoidal_Datum / Ellipsoidal_Referenceframe.
379+
if (!ellipsoid || !ellipsoid.a) throw new TypeError(`invalid ellipsoid ‘${ellipsoid}’`);
380380

381381
const { x, y, z } = this;
382382
const { a, b, f } = ellipsoid;

‎test/latlon-ellipsoidal-datum-tests.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ describe('latlon-ellipsoidal-datum', function() {
6767
test('toCartesian', () => p.toCartesian().toString().should.equal('[3194419,3194419,4487348]'));
6868
const c = new Cartesian(3194419, 3194419, 4487348);
6969
test('toLatLon', () => c.toLatLon().toString().should.equal('45.0000°N, 045.0000°E'));
70-
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'unrecognised datum ‘null’'));
7170
test('toLatLon fail', () => should.Throw(function() { c.toLatLon('xx'); }, TypeError, 'unrecognised datum ‘xx’'));
7271
});
7372
});

‎test/latlon-ellipsoidal-tests.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ describe('latlon-ellipsoidal', function() {
126126

127127
describe('cartesian', function() {
128128
const p = new LatLon(45, 45);
129-
test('toCartesian', () => p.toCartesian().toString().should.equal('[3194419,3194419,4487348]'));
129+
test('toCartesian', () => p.toCartesian().toString().should.equal('[3194419,3194419,4487348]'));
130130
const c = new Cartesian(3194419, 3194419, 4487348);
131-
test('toLatLon', () => c.toLatLon().toString().should.equal('45.0000°N, 045.0000°E'));
132-
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'invalid ellipsoid'));
131+
test('toLatLon', () => c.toLatLon().toString().should.equal('45.0000°N, 045.0000°E'));
132+
test('toLatLon w/ ellipse', () => c.toLatLon(LatLon.datums.WGS84.ellipsoid).toString().should.equal('45.0000°N, 045.0000°E'));
133+
test('toLatLon fail (null)', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'invalid ellipsoid ‘null’'));
134+
test('toLatLon fail (str)', () => should.Throw(function() { c.toLatLon('WGS84'); }, TypeError, 'invalid ellipsoid ‘WGS84’'));
133135
});
134136
});

0 commit comments

Comments
 (0)
Please sign in to comment.