/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Geodesy tools for conversions between reference frames (c) Chris Veness 2016-2019 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/latlong-convert-coords.html */
/* www.movable-type.co.uk/scripts/geodesy-library.html#latlon-ellipsoidal-referenceframe */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
import LatLonEllipsoidal, { Cartesian, Dms } from './latlon-ellipsoidal.js';
/**
* Modern geodetic reference frames: a latitude/longitude point defines a geographic location on or
* above/below the earth’s surface, measured in degrees from the equator and the International
* Reference Meridian and metres above the ellipsoid within a given terrestrial reference frame at a
* given epoch.
*
* This module extends the core latlon-ellipsoidal module to include methods for converting between
* different reference frames.
*
* This is scratching the surface of complexities involved in high precision geodesy, but may be of
* interest and/or value to those with less demanding requirements.
*
* Note that ITRF solutions do not directly use an ellipsoid, but are specified by cartesian
* coordinates; the GRS80 ellipsoid is recommended for transformations to geographical coordinates
* (itrf.ensg.ign.fr).
*
* @module latlon-ellipsoidal-referenceframe
*/
/*
* Sources:
*
* - Soler & Snay, “Transforming Positions and Velocities between the International Terrestrial Refer-
* ence Frame of 2000 and North American Datum of 1983”, Journal of Surveying Engineering May 2004;
* www.ngs.noaa.gov/CORS/Articles/SolerSnayASCE.pdf.
*
* - Dawson & Woods, “ITRF to GDA94 coordinate transformations”, Journal of Applied Geodesy 4 (2010);
* www.ga.gov.au/webtemp/image_cache/GA19050.pdf.
*/
/* eslint-disable key-spacing, indent */
/*
* Ellipsoid parameters; exposed through static getter below.
*/
const ellipsoids = {
WGS84: { a: 6378137, b: 6356752.314245, f: 1/298.257223563 },
GRS80: { a: 6378137, b: 6356752.314140, f: 1/298.257222101 },
};
/*
* Reference frames; exposed through static getter below.
*/
const referenceFrames = {
ITRF2014: { name: 'ITRF2014', epoch: 2010.0, ellipsoid: ellipsoids.GRS80 },
ITRF2008: { name: 'ITRF2008', epoch: 2005.0, ellipsoid: ellipsoids.GRS80 },
ITRF2005: { name: 'ITRF2005', epoch: 2000.0, ellipsoid: ellipsoids.GRS80 },
ITRF2000: { name: 'ITRF2000', epoch: 1997.0, ellipsoid: ellipsoids.GRS80 },
ITRF93: { name: 'ITRF93', epoch: 1988.0, ellipsoid: ellipsoids.GRS80 },
ITRF91: { name: 'ITRF91', epoch: 1988.0, ellipsoid: ellipsoids.GRS80 },
WGS84g1762: { name: 'WGS84g1762', epoch: 2005.0, ellipsoid: ellipsoids.WGS84 },
WGS84g1674: { name: 'WGS84g1674', epoch: 2005.0, ellipsoid: ellipsoids.WGS84 },
WGS84g1150: { name: 'WGS84g1150', epoch: 2001.0, ellipsoid: ellipsoids.WGS84 },
ETRF2000: { name: 'ETRF2000', epoch: 2005.0, ellipsoid: ellipsoids.GRS80 }, // ETRF2000(R08)
NAD83: { name: 'NAD83', epoch: 1997.0, ellipsoid: ellipsoids.GRS80 }, // CORS96
GDA94: { name: 'GDA94', epoch: 1994.0, ellipsoid: ellipsoids.GRS80 },
};
/*
* Transform parameters; exposed through static getter below.
*/
import txParams from './latlon-ellipsoidal-referenceframe-txparams.js';
// freeze static properties
Object.keys(ellipsoids).forEach(e => Object.freeze(ellipsoids[e]));
Object.keys(referenceFrames).forEach(trf => Object.freeze(referenceFrames[trf]));
Object.keys(txParams).forEach(tx => { Object.freeze(txParams[tx]); Object.freeze(txParams[tx].params); Object.freeze(txParams[tx].rates); });
/* eslint-enable key-spacing, indent */
/* LatLon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Latitude/longitude points on an ellipsoidal model earth, with ellipsoid parameters and methods
* for converting between reference frames and to geocentric (ECEF) cartesian coordinates.
*
* @extends LatLonEllipsoidal
*/
class LatLonEllipsoidal_ReferenceFrame extends LatLonEllipsoidal {
/**
* Creates geodetic latitude/longitude point on an ellipsoidal model earth using using a
* specified reference frame.
*
* Note that while the epoch defaults to the frame reference epoch, the accuracy of ITRF
* realisations is meaningless without knowing the observation epoch.
*
* @param {number} lat - Geodetic latitude in degrees.
* @param {number} lon - Geodetic longitude in degrees.
* @param {number} [height=0] - Height above ellipsoid in metres.
* @param {LatLon.referenceFrames} [referenceFrame=ITRF2014] - Reference frame this point is defined within.
* @param {number} [epoch=referenceFrame.epoch] - date of observation of coordinate (decimal year).
* defaults to reference epoch t₀ of reference frame.
* @throws {TypeError} Unrecognised reference frame.
*
* @example
* import LatLon from '/js/geodesy/latlon-ellipsoidal-referenceframe.js';
* const p = new LatLon(51.47788, -0.00147, 0, LatLon.referenceFrames.ITRF2000);
*/
constructor(lat, lon, height=0, referenceFrame=referenceFrames.ITRF2014, epoch=undefined) {
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame');
if (epoch != undefined && isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`);
super(lat, lon, height);
this._referenceFrame = referenceFrame;
if (epoch) this._epoch = Number(epoch);
}
/**
* Reference frame this point is defined within.
*/
get referenceFrame() {
return this._referenceFrame;
}
/**
* Point’s observed epoch.
*/
get epoch() {
return this._epoch || this.referenceFrame.epoch;
}
/**
* Ellipsoid parameters; semi-major axis (a), semi-minor axis (b), and flattening (f).
*
* The only ellipsoids used in modern geodesy are WGS-84 and GRS-80 (while based on differing
* defining parameters, the only effective difference is a 0.1mm variation in the minor axis b).
*
* @example
* const availableEllipsoids = Object.keys(LatLon.ellipsoids).join(); // WGS84,GRS80
* const a = LatLon.ellipsoids.Airy1830.a; // 6377563.396
*/
static get ellipsoids() {
return ellipsoids;
}
/**
* Reference frames, with their base ellipsoids and reference epochs.
*
* @example
* const availableReferenceFrames = Object.keys(LatLon.referenceFrames).join(); // ITRF2014,ITRF2008, ...
*/
static get referenceFrames() {
return referenceFrames;
}
/**
* 14-parameter Helmert transformation parameters between (dynamic) ITRS frames, and from ITRS
* frames to (static) regional TRFs NAD83, ETRF2000, and GDA94.
*
* This is a limited set of transformations; e.g. ITRF frames prior to ITRF2000 are not included.
* More transformations could be added on request.
*
* Many conversions are direct; for NAD83, successive ITRF transformations are chained back to
* ITRF2000.
*/
static get transformParameters() {
return txParams;
}
/**
* Parses a latitude/longitude point from a variety of formats.
*
* Latitude & longitude (in degrees) can be supplied as two separate parameters, as a single
* comma-separated lat/lon string, or as a single object with { lat, lon } or GeoJSON properties.
*
* The latitude/longitude values may be numeric or strings; they may be signed decimal or
* deg-min-sec (hexagesimal) suffixed by compass direction (NSEW); a variety of separators are
* accepted. Examples -3.62, '3 37 12W', '3°37′12″W'.
*
* Thousands/decimal separators must be comma/dot; use Dms.fromLocale to convert locale-specific
* thousands/decimal separators.
*
* @param {number|string|Object} lat|latlon - Geodetic Latitude (in degrees) or comma-separated lat/lon or lat/lon object.
* @param {number} [lon] - Longitude in degrees.
* @param {number} height - Height above ellipsoid in metres.
* @param {LatLon.referenceFrames} referenceFrame - Reference frame this point is defined within.
* @param {number} [epoch=referenceFrame.epoch] - date of observation of coordinate (decimal year).
* @returns {LatLon} Latitude/longitude point on ellipsoidal model earth using given reference frame.
* @throws {TypeError} Unrecognised reference frame.
*
* @example
* const p1 = LatLon.parse(51.47788, -0.00147, 17, LatLon.referenceFrames.ETRF2000); // numeric pair
* const p2 = LatLon.parse('51°28′40″N, 000°00′05″W', 17, LatLon.referenceFrames.ETRF2000); // dms string + height
* const p3 = LatLon.parse({ lat: 52.205, lon: 0.119 }, 17, LatLon.referenceFrames.ETRF2000); // { lat, lon } object numeric
*/
static parse(...args) {
if (args.length == 0) throw new TypeError('invalid (empty) point');
let referenceFrame = null, epoch = null;
if (!isNaN(args[1]) && typeof args[2] == 'object') { // latlon, height, referenceFrame, [epoch]
[ referenceFrame ] = args.splice(2, 1);
[ epoch ] = args.splice(2, 1);
}
if (!isNaN(args[2]) && typeof args[3] == 'object') { // lat, lon, height, referenceFrame, [epoch]
[ referenceFrame ] = args.splice(3, 1);
[ epoch ] = args.splice(3, 1);
}
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame');
// args is now lat, lon, height or latlon, height as taken by LatLonEllipsoidal .parse()
const point = super.parse(...args); // note super.parse() also invokes this.constructor()
point._referenceFrame = referenceFrame;
if (epoch) point._epoch = Number(epoch);
return point;
}
/**
* Converts ‘this’ lat/lon coordinate to new coordinate system.
*
* @param {LatLon.referenceFrames} toReferenceFrame - Reference frame this coordinate is to be converted to.
* @returns {LatLon} This point converted to new reference frame.
* @throws {Error} Undefined reference frame, Transformation not available.
*
* @example
* const pEtrf = new LatLon(51.47788000, -0.00147000, 0, LatLon.referenceFrames.ITRF2000);
* const pItrf = pEtrf.convertReferenceFrame(LatLon.referenceFrames.ETRF2000); // 51.47787826°N, 000.00147125°W
*/
convertReferenceFrame(toReferenceFrame) {
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('unrecognised reference frame');
const oldCartesian = this.toCartesian(); // convert geodetic to cartesian
const newCartesian = oldCartesian.convertReferenceFrame(toReferenceFrame); // convert TRF
const newLatLon = newCartesian.toLatLon(); // convert cartesian back to to geodetic
return newLatLon;
}
/**
* Converts ‘this’ point from (geodetic) latitude/longitude coordinates to (geocentric) cartesian
* (x/y/z) coordinates, based on same reference frame.
*
* Shadow of LatLonEllipsoidal.toCartesian(), returning Cartesian augmented with
* LatLonEllipsoidal_ReferenceFrame methods/properties.
*
* @returns {Cartesian} Cartesian point equivalent to lat/lon point, with x, y, z in metres from
* earth centre, augmented with reference frame conversion methods and properties.
*/
toCartesian() {
const cartesian = super.toCartesian();
const cartesianReferenceFrame = new Cartesian_ReferenceFrame(cartesian.x, cartesian.y, cartesian.z, this.referenceFrame, this.epoch);
return cartesianReferenceFrame;
}
/**
* Returns a string representation of ‘this’ point, formatted as degrees, degrees+minutes, or
* degrees+minutes+seconds.
*
* @param {string} [format=d] - Format point as 'd', 'dm', 'dms'.
* @param {number} [dp=4|2|0] - Number of decimal places to use: default 4 for d, 2 for dm, 0 for dms.
* @param {number} [dpHeight=null] - Number of decimal places to use for height; default (null) is no height display.
* @param {boolean} [referenceFrame=false] - Whether to show reference frame point is defined on.
* @returns {string} Comma-separated formatted latitude/longitude.
*
* @example
* new LatLon(51.47788, -0.00147, 0, LatLon.referenceFrames.ITRF2014).toString(); // 51.4778°N, 000.0015°W
* new LatLon(51.47788, -0.00147, 0, LatLon.referenceFrames.ITRF2014).toString('dms'); // 51°28′40″N, 000°00′05″W
* new LatLon(51.47788, -0.00147, 42, LatLon.referenceFrames.ITRF2014).toString('dms', 0, 0); // 51°28′40″N, 000°00′05″W +42m
*/
toString(format='d', dp=undefined, dpHeight=null, referenceFrame=false) {
const ll = super.toString(format, dp, dpHeight);
const epochFmt = { useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 20 };
const epoch = this.referenceFrame && this.epoch != this.referenceFrame.epoch ? this.epoch.toLocaleString('en', epochFmt) : '';
const trf = referenceFrame ? ` (${this.referenceFrame.name}${epoch?'@'+epoch:''})` : '';
return ll + trf;
}
}
/* Cartesian - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Augments Cartesian with reference frame and observation epoch the cooordinate is based on, and
* methods to convert between reference frames (using Helmert 14-parameter transforms) and to
* convert cartesian to geodetic latitude/longitude point.
*
* @extends Cartesian
*/
class Cartesian_ReferenceFrame extends Cartesian {
/**
* Creates cartesian coordinate representing ECEF (earth-centric earth-fixed) point, on a given
* reference frame. The reference frame will identify the primary meridian (for the x-coordinate),
* and is also useful in transforming to/from geodetic (lat/lon) coordinates.
*
* @param {number} x - X coordinate in metres (=> 0°N,0°E).
* @param {number} y - Y coordinate in metres (=> 0°N,90°E).
* @param {number} z - Z coordinate in metres (=> 90°N).
* @param {LatLon.referenceFrames} [referenceFrame] - Reference frame this coordinate is defined within.
* @param {number} [epoch=referenceFrame.epoch] - date of observation of coordinate (decimal year).
* @throws {TypeError} Unrecognised reference frame, Invalid epoch.
*
* @example
* import { Cartesian } from '/js/geodesy/latlon-ellipsoidal-referenceframe.js';
* const coord = new Cartesian(3980581.210, -111.159, 4966824.522);
*/
constructor(x, y, z, referenceFrame=undefined, epoch=undefined) {
if (referenceFrame!=undefined && referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame');
if (epoch!=undefined && isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`);
super(x, y, z);
if (referenceFrame) this._referenceFrame = referenceFrame;
if (epoch) this._epoch = epoch;
}
/**
* Reference frame this point is defined within.
*/
get referenceFrame() {
return this._referenceFrame;
}
set referenceFrame(referenceFrame) {
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame');
this._referenceFrame = referenceFrame;
}
/**
* Point’s observed epoch.
*/
get epoch() {
return this._epoch ? this._epoch : (this._referenceFrame ? this._referenceFrame.epoch : undefined);
}
set epoch(epoch) {
if (isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`);
if (this._epoch != this._referenceFrame.epoch) this._epoch = Number(epoch);
}
/**
* Converts ‘this’ (geocentric) cartesian (x/y/z) coordinate to (geodetic) latitude/longitude
* point (based on the same reference frame).
*
* Shadow of Cartesian.toLatLon(), returning LatLon augmented with LatLonEllipsoidal_ReferenceFrame
* methods convertReferenceFrame, toCartesian, etc.
*
* @returns {LatLon} Latitude/longitude point defined by cartesian coordinates, in given reference frame.
* @throws {Error} No reference frame defined.
*
* @example
* const c = new Cartesian(4027893.924, 307041.993, 4919474.294, LatLon.referenceFrames.ITRF2000);
* const p = c.toLatLon(); // 50.7978°N, 004.3592°E
*/
toLatLon() {
if (!this.referenceFrame) throw new Error('cartesian reference frame not defined');
const latLon = super.toLatLon(this.referenceFrame.ellipsoid);
const point = new LatLonEllipsoidal_ReferenceFrame(latLon.lat, latLon.lon, latLon.height, this.referenceFrame, this.epoch);
return point;
}
/**
* Converts ‘this’ cartesian coordinate to new reference frame using Helmert 14-parameter
* transformation. The observation epoch is unchanged.
*
* Note that different conversions have different tolerences; refer to the literature if
* tolerances are significant.
*
* @param {LatLon.referenceFrames} toReferenceFrame - Reference frame this coordinate is to be converted to.
* @returns {Cartesian} This point converted to new reference frame.
* @throws {Error} Undefined reference frame.
*
* @example
* const c = new Cartesian(3980574.247, -102.127, 4966830.065, LatLon.referenceFrames.ITRF2000);
* c.convertReferenceFrame(LatLon.referenceFrames.ETRF2000); // [3980574.395,-102.214,4966829.941](ETRF2000@1997.0)
*/
convertReferenceFrame(toReferenceFrame) {
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('unrecognised reference frame');
if (!this.referenceFrame) throw new TypeError('cartesian coordinate has no reference frame');
if (this.referenceFrame.name == toReferenceFrame.name) return this; // no-op!
const oldTrf = this.referenceFrame;
const newTrf = toReferenceFrame;
// WGS84(G730/G873/G1150) are coincident with ITRF at 10-centimetre level; WGS84(G1674) and
// ITRF20014 / ITRF2008 ‘are likely to agree at the centimeter level’ (QPS)
if (oldTrf.name.startsWith('ITRF') && newTrf.name.startsWith('WGS84')) return this;
if (oldTrf.name.startsWith('WGS84') && newTrf.name.startsWith('ITRF')) return this;
const oldC = this;
let newC = null;
// is requested transformation available in single step?
const txFwd = txParams[oldTrf.name+'→'+newTrf.name];
const txRev = txParams[newTrf.name+'→'+oldTrf.name];
if (txFwd || txRev) {
// yes, single step available (either forward or reverse)
const tx = txFwd? txFwd : reverseTransform(txRev);
const t = this.epoch || this.referenceFrame.epoch;
const t0 = tx.epoch;//epoch || newTrf.epoch;
newC = oldC.applyTransform(tx.params, tx.rates, t-t0); // ...apply transform...
} else {
// find intermediate transform common to old & new to chain though; this is pretty yucky,
// but since with current transform params we can transform in no more than 2 steps, it works!
// TODO: find cleaner method!
const txAvailFromOld = Object.keys(txParams).filter(tx => tx.split('→')[0] == oldTrf.name).map(tx => tx.split('→')[1]);
const txAvailToNew = Object.keys(txParams).filter(tx => tx.split('→')[1] == newTrf.name).map(tx => tx.split('→')[0]);
const txIntermediateFwd = txAvailFromOld.filter(tx => txAvailToNew.includes(tx))[0];
const txAvailFromNew = Object.keys(txParams).filter(tx => tx.split('→')[0] == newTrf.name).map(tx => tx.split('→')[1]);
const txAvailToOld = Object.keys(txParams).filter(tx => tx.split('→')[1] == oldTrf.name).map(tx => tx.split('→')[0]);
const txIntermediateRev = txAvailFromNew.filter(tx => txAvailToOld.includes(tx))[0];
const txFwd1 = txParams[oldTrf.name+'→'+txIntermediateFwd];
const txFwd2 = txParams[txIntermediateFwd+'→'+newTrf.name];
const txRev1 = txParams[newTrf.name+'→'+txIntermediateRev];
const txRev2 = txParams[txIntermediateRev+'→'+oldTrf.name];
const tx1 = txIntermediateFwd ? txFwd1 : reverseTransform(txRev2);
const tx2 = txIntermediateFwd ? txFwd2 : reverseTransform(txRev1);
const t = this.epoch || this.referenceFrame.epoch;
newC = oldC.applyTransform(tx1.params, tx1.rates, t-tx1.epoch); // ...apply transform 1...
newC = newC.applyTransform(tx2.params, tx2.rates, t-tx2.epoch); // ...apply transform 2...
}
newC.referenceFrame = toReferenceFrame;
newC.epoch = oldC.epoch;
return newC;
function reverseTransform(tx) {
return { epoch: tx.epoch, params: tx.params.map(p => -p), rates: tx.rates.map(r => -r) };
}
}
/**
* Applies Helmert 14-parameter transformation to ‘this’ coordinate using supplied transform
* parameters and annual rates of change, with the secular variation given by the difference
* between the reference epoch t0 and the observation epoch tc.
*
* This is used in converting reference frames.
*
* See e.g. 3D Coordinate Transformations, Deakin, 1998.
*
* @private
* @param {number[]} params - Transform parameters tx, ty, tz, s, rx, ry, rz..
* @param {number[]} rates - Rate of change of transform parameters ṫx, ṫy, ṫz, ṡ, ṙx, ṙy, ṙz.
* @param {number} δt - Period between reference and observed epochs, t − t₀.
* @returns {Cartesian} Transformed point (without reference frame).
*/
applyTransform(params, rates, δt) {
// this point
const x1 = this.x, y1 = this.y, z1 = this.z;
// base parameters
const tx = params[0]/1000; // x-shift: normalise millimetres to metres
const ty = params[1]/1000; // y-shift: normalise millimetres to metres
const tz = params[2]/1000; // z-shift: normalise millimetres to metres
const s = params[3]/1e9; // scale: normalise parts-per-billion
const rx = (params[4]/3600/1000).toRadians(); // x-rotation: normalise milliarcseconds to radians
const ry = (params[5]/3600/1000).toRadians(); // y-rotation: normalise milliarcseconds to radians
const rz = (params[6]/3600/1000).toRadians(); // z-rotation: normalise milliarcseconds to radians
// rate parameters
const ṫx = rates[0]/1000; // x-shift: normalise millimetres to metres
const ṫy = rates[1]/1000; // y-shift: normalise millimetres to metres
const ṫz = rates[2]/1000; // z-shift: normalise millimetres to metres
const ṡ = rates[3]/1e9; // scale: normalise parts-per-billion
const ṙx = (rates[4]/3600/1000).toRadians(); // x-rotation: normalise milliarcseconds to radians
const ṙy = (rates[5]/3600/1000).toRadians(); // y-rotation: normalise milliarcseconds to radians
const ṙz = (rates[6]/3600/1000).toRadians(); // z-rotation: normalise milliarcseconds to radians
// combined (normalised) parameters
const T = { x: tx + ṫx*δt, y: ty + ṫy*δt, z: tz + ṫz*δt };
const R = { x: rx + ṙx*δt, y: ry + ṙy*δt, z: rz + ṙz*δt };
const S = 1 + s + ṡ*δt;
// apply transform (shift, scale, rotate)
const x2 = T.x + x1*S - y1*R.z + z1*R.y;
const y2 = T.y + x1*R.z + y1*S - z1*R.x;
const z2 = T.z - x1*R.y + y1*R.x + z1*S;
return new Cartesian_ReferenceFrame(x2, y2, z2);
}
/**
* Returns a string representation of ‘this’ cartesian point. TRF is shown if set, and
* observation epoch if different from reference epoch.
*
* @param {number} [dp=0] - Number of decimal places to use.
* @returns {string} Comma-separated latitude/longitude.
*/
toString(dp=0) {
const { x, y, z } = this;
const epochFmt = { useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 20 };
const epoch = this.referenceFrame && this.epoch != this.referenceFrame.epoch ? this.epoch.toLocaleString('en', epochFmt) : '';
const trf = this.referenceFrame ? `(${this.referenceFrame.name}${epoch?'@'+epoch:''})` : '';
return `[${x.toFixed(dp)},${y.toFixed(dp)},${z.toFixed(dp)}]${trf}`;
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
export { LatLonEllipsoidal_ReferenceFrame as default, Cartesian_ReferenceFrame as Cartesian, Dms };