gnss_solutions
Version:
Javascript GNSS solution analysis library
423 lines (380 loc) • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SolutionStatus = exports.LLASolution = exports.ECEFBaseline = exports.ECEFSolution = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /*
* Copyright (c) 2016 Swift Navigation Inc.
* Contact: engineering@swiftnav.com
*
* This source is subject to the license found in the file 'LICENSE' which must
* be be distributed together with this source. All other rights reserved.
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
*/
var _errors = require("./errors");
var err = _interopRequireWildcard(_errors);
var _ecefProjector = require("ecef-projector");
var projector = _interopRequireWildcard(_ecefProjector);
var _constants = require("./constants");
var sym = _interopRequireWildcard(_constants);
var _geodesy = require("geodesy");
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* ECEF position: x, y, z
*
* TODO: associate a datum and epoch-of-datum
* TODO (Buro): Add some manner of Point definition from other JavaScript
* solution libraries.
*
* @param {Number} x - X parameter (meters)
* @param {Number} y - Y parameter (meters)
* @param {Number} z - Z parameter (meters)
*/
var ECEFSolution = exports.ECEFSolution = function () {
function ECEFSolution(x, y, z) {
_classCallCheck(this, ECEFSolution);
this.x = x;
this.y = y;
this.z = z;
}
_createClass(ECEFSolution, [{
key: "distanceTo",
value: function distanceTo(b) {
var dx = this.x - b.x;
var dy = this.y - b.y;
var dz = this.z - b.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
}, {
key: "toLLA",
value: function toLLA() {
var v = projector.unproject(this.x, this.y, this.z);
return new LLASolution(v[0], sym.POLE_NORTH, v[1], sym.POLE_EAST, v[2]);
}
}, {
key: "toJSON",
value: function toJSON() {
var _ref;
return _ref = {}, _defineProperty(_ref, sym.SAMPLE_X, this.x), _defineProperty(_ref, sym.SAMPLE_Y, this.y), _defineProperty(_ref, sym.SAMPLE_Z, this.z), _ref;
}
}, {
key: "toString",
value: function toString() {
return JSON.stringify(this.toJSON());
}
}], [{
key: "objHasValidECEF",
value: function objHasValidECEF(obj) {
return typeof obj[sym.SAMPLE_X] === 'number' && typeof obj[sym.SAMPLE_Y] === 'number' && typeof obj[sym.SAMPLE_Z] === 'number';
}
}, {
key: "fromJSON",
value: function fromJSON(obj) {
if (!ECEFSolution.objHasValidECEF(obj)) {
return null;
}
return new ECEFSolution(obj[sym.SAMPLE_X], obj[sym.SAMPLE_Y], obj[sym.SAMPLE_Z]);
}
}, {
key: "fromMap",
value: function fromMap(map) {
return new ECEFSolution(map.get(sym.SAMPLE_X), map.get(sym.SAMPLE_Y), map.get(sym.SAMPLE_Z));
}
}]);
return ECEFSolution;
}();
/**
* Baseline vector in ECEF: x, y, z
*
* @param {Number} x - X parameter (meters)
* @param {Number} y - Y parameter (meters)
* @param {Number} z - Z parameter (meters)
*/
var ECEFBaseline = exports.ECEFBaseline = function () {
function ECEFBaseline(dx, dy, dz) {
_classCallCheck(this, ECEFBaseline);
this.dx = dx;
this.dy = dy;
this.dz = dz;
}
_createClass(ECEFBaseline, [{
key: "norm",
value: function norm() {
if (isNaN(this.dx) || isNaN(this.dy) || isNaN(this.dz)) {
return 0;
}
return Math.sqrt(this.dx * this.dx + this.dy * this.dy + this.dz * this.dz);
}
}, {
key: "toJSON",
value: function toJSON() {
var _ref2;
return _ref2 = {}, _defineProperty(_ref2, sym.BASELINE_X, this.dx), _defineProperty(_ref2, sym.BASELINE_Y, this.dy), _defineProperty(_ref2, sym.BASELINE_Z, this.dz), _ref2;
}
}, {
key: "toString",
value: function toString() {
return JSON.stringify(this.toJSON());
}
}], [{
key: "objHasValidECEF",
value: function objHasValidECEF(obj) {
return typeof obj[sym.BASELINE_X] === 'number' && typeof obj[sym.BASELINE_Y] === 'number' && typeof obj[sym.BASELINE_Z] === 'number';
}
}, {
key: "fromJSON",
value: function fromJSON(obj) {
if (!ECEFBaseline.objHasValidECEF(obj)) {
return null;
}
return new ECEFBaseline(obj[sym.BASELINE_X], obj[sym.BASELINE_Y], obj[sym.BASELINE_Z]);
}
}, {
key: "fromMap",
value: function fromMap(map) {
return new ECEFBaseline(map.get(sym.BASELINE_X), map.get(sym.BASELINE_Y), map.get(sym.BASELINE_Z));
}
}]);
return ECEFBaseline;
}();
/**
* Latitude, longitude, ellipsoid altitude
*
* @param {Number} lat - latitude (decimal degrees)
* @param {String} latPole - latitude pole: 'N' or 'S'
* @param {Number} lon - longitude (decimal degrees)
* @param {String} lonPole - longitude pole: 'W' or 'E'
* @param {Number} alt - altitude (meters)
*/
var LLASolution = exports.LLASolution = function () {
function LLASolution(lat, latPole, lon, lonPole, alt) {
_classCallCheck(this, LLASolution);
if (latPole !== sym.POLE_SOUTH && latPole !== sym.POLE_NORTH) {
throw new err.InvalidArgumentException("Invalid latitude pole: " + latPole);
}
if (lonPole !== sym.POLE_EAST && lonPole !== sym.POLE_WEST) {
throw new err.InvalidArgumentException("Invalid longitude pole." + lonPole);
}
// Google Maps expects everything to be N, E
this.lat = lat * (latPole === sym.POLE_SOUTH ? -1 : 1);
this.latPole = sym.POLE_NORTH;
this.lon = lon * (lonPole === sym.POLE_WEST ? -1 : 1);
this.lonPole = sym.POLE_EAST;
this.alt = alt;
}
_createClass(LLASolution, [{
key: "distanceTo",
value: function distanceTo(b) {
var aLL = new _geodesy.LatLonEllipsoidal(this.lat, this.lon);
var bLL = new _geodesy.LatLonEllipsoidal(b.lat, b.lon);
var circleDist = aLL.distanceTo(bLL);
var verticalDist = this.alt - b.alt;
return [circleDist, verticalDist];
}
/**
* Convert to ECEF frame.
*
* @return {ECEFSolution}
*/
}, {
key: "toECEF",
value: function toECEF() {
var v = projector.project(this.lat, this.lon, this.alt);
return new ECEFSolution(v[0], v[1], v[2]);
}
}, {
key: "toJSON",
// TODO (Buro): change this from height to altitude
value: function toJSON() {
var _ref3;
return _ref3 = {}, _defineProperty(_ref3, sym.SAMPLE_LAT, this.lat), _defineProperty(_ref3, sym.SAMPLE_LON, this.lon), _defineProperty(_ref3, sym.SAMPLE_HGT, this.alt), _ref3;
}
}, {
key: "toString",
value: function toString() {
return JSON.stringify(this.toJSON());
}
}], [{
key: "objHasValidLLA",
value: function objHasValidLLA(obj) {
return typeof obj[sym.SAMPLE_LAT] === 'number' && typeof obj[sym.SAMPLE_LON] === 'number' && typeof obj[sym.SAMPLE_HGT] === 'number';
}
}, {
key: "fromJSON",
value: function fromJSON(obj) {
if (!LLASolution.objHasValidLLA(obj)) {
return null;
}
return new LLASolution(obj[sym.SAMPLE_LAT], sym.POLE_NORTH, obj[sym.SAMPLE_LON], sym.POLE_EAST, obj[sym.SAMPLE_HGT]);
}
}, {
key: "fromMap",
value: function fromMap(map) {
return new LLASolution(map.get(sym.SAMPLE_LAT), sym.POLE_NORTH, map.get(sym.SAMPLE_LON), sym.POLE_EAST, map.get(sym.SAMPLE_HGT));
}
// TODO(mookerji): should this actually be constructing the height above
// mean-sea-level using the derived geoid height?
/**
* Materialize a LLASolution from a DMS string. Used here is the ellipsoidal
* height.
*
* For more details, see https://www.ngs.noaa.gov/PUBS_LIB/gislis96.html.
*
* @param {String} latStr - Latitude (DMS)
* @param {String} lonStr - Longitude (DMS)
* @param {Number} height - Ellipsoidal height (meters)
* @return {LLASolution}
*/
}, {
key: "fromDMS",
value: function fromDMS(latStr, lonStr, height) {
var lat = _geodesy.Dms.parseDMS(latStr);
var lon = _geodesy.Dms.parseDMS(lonStr);
return new LLASolution(lat, sym.POLE_NORTH, lon, sym.POLE_EAST, height);
}
}]);
return LLASolution;
}();
/**
* A solution status represents a position solution from a sample GNSS receiver,
* and additionally keeps track of a few other parameters of interest reported
* during that epoch. It represents a sample solution at a particular instance
* of time, and stores a flat map for a particular instance in time, using the
* result schema defined by Swift's gnss-testing repository:
* https://github.com/swift-nav/gnss-testing/blob/master/DESIGN.md#swift-solution-csv
*
* Example:
* ```
* import { ECEFSolution, SolutionStatus } from "./coords";
* const time = new Date('2016-06-05');
* const ecef = new coords.ECEFSolution(ctest.earthA, 0, 0);
* const soln = new coords.SolutionStatus(time, ecef.toLLA(), 0, 3, 0.5);
* console.log(event.toJSON()); =>
* {"epoch(gpst)": new Date("2016-06-05T00:00:00.000Z"),
* "fix_mode": "integer_rtk",
* "latency(sec)": 0.5,
* "num_sats": 3,
* "est_error_2d(m)": NaN,
* "est_error_3d(m)": NaN,
* "est_error_v(m)": NaN,
* "baseline_x(m)": NaN,
* "baseline_y(m)": NaN,
* "baseline_z(m)": NaN,
* "rover_pos_x(m)":6378137,
* "rover_pos_y(m)":0,
* "rover_pos_z(m)":0,
* "rover_pos_lat(deg)":0,
* "rover_pos_lon(deg)":0,
* "rover_pos_height(m)":0}
* ```
*/
var SolutionStatus = exports.SolutionStatus = function () {
function SolutionStatus(time, ecef, lla) {
var fixMode = arguments.length <= 3 || arguments[3] === undefined ? 3 : arguments[3];
var numSats = arguments.length <= 4 || arguments[4] === undefined ? NaN : arguments[4];
var latency = arguments.length <= 5 || arguments[5] === undefined ? NaN : arguments[5];
var ecefBaseline = arguments.length <= 6 || arguments[6] === undefined ? new ECEFBaseline(NaN, NaN, NaN) : arguments[6];
var estHError = arguments.length <= 7 || arguments[7] === undefined ? NaN : arguments[7];
var estVError = arguments.length <= 8 || arguments[8] === undefined ? NaN : arguments[8];
var estSphError = arguments.length <= 9 || arguments[9] === undefined ? NaN : arguments[9];
_classCallCheck(this, SolutionStatus);
if (!ecef && !lla) {
throw new err.InvalidArgumentException("Missing a position argument!");
}
this.time = time;
this.ecefSolution = ecef;
this.llaSolution = lla;
this.fixMode = fixMode;
this.numSats = numSats;
this.latency = latency;
this.ecefBaseline = ecefBaseline;
this.estHError = estHError;
this.estVError = estVError;
this.estSphError = estSphError;
}
_createClass(SolutionStatus, [{
key: "toJSON",
value: function toJSON() {
var _Object$assign;
return Object.assign((_Object$assign = {}, _defineProperty(_Object$assign, sym.GPS_TIME, this.time.toISOString()), _defineProperty(_Object$assign, sym.FIX_MODE, sym.fixModeToString[this.fixMode]), _defineProperty(_Object$assign, sym.NUM_SATS, this.numSats), _defineProperty(_Object$assign, sym.LATENCY, this.latency), _defineProperty(_Object$assign, sym.EST_H_ERR, this.estHError), _defineProperty(_Object$assign, sym.EST_V_ERR, this.estVError), _defineProperty(_Object$assign, sym.EST_SPH_ERR, this.estSphError), _Object$assign), this.getECEF() && this.getECEF().toJSON() || {}, this.getLLA() && this.getLLA().toJSON() || {}, this.ecefBaseline && this.ecefBaseline.toJSON() || {});
}
}, {
key: "compareTo",
value: function compareTo(ref) {
var absError3d = NaN,
absErrorH = NaN,
absErrorV = NaN;
if (this.getECEF() && ref.getECEF()) {
absError3d = this.getECEF().distanceTo(ref.getECEF());
}
if (this.getLLA() && ref.getLLA()) {
var _getLLA$distanceTo = this.getLLA().distanceTo(ref.getLLA());
var _getLLA$distanceTo2 = _slicedToArray(_getLLA$distanceTo, 2);
absErrorH = _getLLA$distanceTo2[0];
absErrorV = _getLLA$distanceTo2[1];
}
return [absError3d, absErrorH, absErrorV];
}
}, {
key: "toString",
value: function toString() {
return JSON.stringify(this.toJSON());
}
}, {
key: "getLLA",
value: function getLLA() {
if (this.llaSolution) {
return this.llaSolution;
}
if (!this.computedLla) {
this.computedLla = this.ecefSolution.toLLA();
}
return this.computedLla;
}
}, {
key: "getECEF",
value: function getECEF() {
if (this.ecefSolution) {
return this.ecefSolution;
}
if (!this.computedEcef) {
this.computedEcef = this.llaSolution.toECEF();
}
return this.computedEcef;
}
}, {
key: "mergeWith",
value: function mergeWith(other) {
if (other.time.toISOString() !== this.time.toISOString()) {
throw new Error('Cannot merge solution statuses with different timestamps');
}
var lla = this.llaSolution || other.llaSolution;
var ecef = this.ecefSolution || other.ecefSolution;
var fixMode = this.fixMode || other.fixMode;
var numSats = this.numSats || other.numSats;
var latency = this.latency || other.latency;
var ecefBaseline = this.ecefBaseline || other.ecefBaseline;
var estHError = this.estHError || other.estHError;
var estVError = this.estVError || other.estVError;
var estSphError = this.estSphError || other.estSphError;
return new SolutionStatus(this.time, ecef, lla, fixMode, numSats, latency, ecefBaseline, estHError, estVError, estSphError);
}
}], [{
key: "fromJSON",
value: function fromJSON(obj) {
return new SolutionStatus(new Date(obj[sym.GPS_TIME]), ECEFSolution.fromJSON(obj), LLASolution.fromJSON(obj), sym.fixModeFromString[obj[sym.FIX_MODE]], obj[sym.NUM_SATS], obj[sym.LATENCY], ECEFBaseline.fromJSON(obj), obj[sym.EST_H_ERR], obj[sym.EST_V_ERR], obj[sym.EST_SPH_ERR]);
}
}, {
key: "fromMap",
value: function fromMap(map) {
return new SolutionStatus(new Date(map.get(sym.GPS_TIME)), ECEFSolution.fromMap(map), LLASolution.fromMap(map), sym.fixModeFromString[map.get(sym.FIX_MODE)], map.get(sym.NUM_SATS), map.get(sym.LATENCY), ECEFBaseline.fromMap(map), map.get(sym.EST_H_ERR), map.get(sym.EST_V_ERR), map.get(sym.EST_SPH_ERR));
}
}]);
return SolutionStatus;
}();