UNPKG

gnss_solutions

Version:

Javascript GNSS solution analysis library

423 lines (380 loc) 23.2 kB
"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; }();