@jbroll/nmea-simple
Version:
NMEA 0183 sentence parser and encoder
336 lines (335 loc) • 10.6 kB
JavaScript
;
// Copied from from https://github.com/nherment/node-nmea/blob/master/lib/Helper.js
Object.defineProperty(exports, "__esModule", { value: true });
var m_hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
function toHexString(v) {
var msn = (v >> 4) & 0x0f;
var lsn = (v >> 0) & 0x0f;
return m_hex[msn] + m_hex[lsn];
}
exports.toHexString = toHexString;
function padLeft(value, length, paddingCharacter) {
var result = typeof value === "string" ? value : value.toFixed(0);
while (result.length < length) {
result = paddingCharacter + result;
}
return result;
}
exports.padLeft = padLeft;
// =========================================
// checksum related functions
// =========================================
/**
* Checks that the given NMEA sentence has a valid checksum.
*/
function validNmeaChecksum(nmeaSentence) {
var _a = nmeaSentence.split("*"), sentenceWithoutChecksum = _a[0], checksumString = _a[1];
var correctChecksum = computeNmeaChecksum(sentenceWithoutChecksum);
// checksum is a 2 digit hex value
var actualChecksum = parseInt(checksumString, 16);
return correctChecksum === actualChecksum;
}
exports.validNmeaChecksum = validNmeaChecksum;
/**
* Generate a checksum for an NMEA sentence without the trailing "*xx".
*/
function computeNmeaChecksum(sentenceWithoutChecksum) {
// init to first character value after the $
var checksum = sentenceWithoutChecksum.charCodeAt(1);
// process rest of characters, zero delimited
for (var i = 2; i < sentenceWithoutChecksum.length; i += 1) {
checksum = checksum ^ sentenceWithoutChecksum.charCodeAt(i);
}
// checksum is between 0x00 and 0xff
checksum = checksum & 0xff;
return checksum;
}
exports.computeNmeaChecksum = computeNmeaChecksum;
/**
* Generate the correct trailing "*xx" footer for an NMEA sentence.
*/
function createNmeaChecksumFooter(sentenceWithoutChecksum) {
return "*" + toHexString(computeNmeaChecksum(sentenceWithoutChecksum));
}
exports.createNmeaChecksumFooter = createNmeaChecksumFooter;
/**
* Append the correct trailing "*xx" footer for an NMEA string and return the result.
*/
function appendChecksumFooter(sentenceWithoutChecksum) {
return sentenceWithoutChecksum + createNmeaChecksumFooter(sentenceWithoutChecksum);
}
exports.appendChecksumFooter = appendChecksumFooter;
// =========================================
// field encoders
// =========================================
function encodeFixed(value, decimalPlaces) {
if (value === undefined) {
return "";
}
return value.toFixed(decimalPlaces);
}
exports.encodeFixed = encodeFixed;
/**
* Encodes the latitude in the standard NMEA format "ddmm.mmmmmm".
*
* @param latitude Latitude in decimal degrees.
*/
function encodeLatitude(latitude) {
if (latitude === undefined) {
return ",";
}
var hemisphere;
if (latitude < 0) {
hemisphere = "S";
latitude = -latitude;
}
else {
hemisphere = "N";
}
// get integer degrees
var d = Math.floor(latitude);
// latitude degrees are always 2 digits
var s = padLeft(d, 2, "0");
// get fractional degrees
var f = latitude - d;
// convert to fractional minutes
var m = (f * 60.0);
// format the fixed point fractional minutes "mm.mmmmmm"
var t = padLeft(m.toFixed(6), 9, "0");
s = s + t + "," + hemisphere;
return s;
}
exports.encodeLatitude = encodeLatitude;
/**
* Encodes the longitude in the standard NMEA format "dddmm.mmmmmm".
*
* @param longitude Longitude in decimal degrees.
*/
function encodeLongitude(longitude) {
if (longitude === undefined) {
return ",";
}
var hemisphere;
if (longitude < 0) {
hemisphere = "W";
longitude = -longitude;
}
else {
hemisphere = "E";
}
// get integer degrees
var d = Math.floor(longitude);
// longitude degrees are always 3 digits
var s = padLeft(d, 3, "0");
// get fractional degrees
var f = longitude - d;
// convert to fractional minutes and round up to the specified precision
var m = (f * 60.0);
// format the fixed point fractional minutes "mm.mmmmmm"
var t = padLeft(m.toFixed(6), 9, "0");
s = s + t + "," + hemisphere;
return s;
}
exports.encodeLongitude = encodeLongitude;
// 1 decimal, always meters
function encodeAltitude(alt) {
if (alt === undefined) {
return ",";
}
return alt.toFixed(1) + ",M";
}
exports.encodeAltitude = encodeAltitude;
// Some encodings don't want the unit
function encodeAltitudeNoUnits(alt) {
if (alt === undefined) {
return "";
}
return alt.toFixed(1);
}
exports.encodeAltitudeNoUnits = encodeAltitudeNoUnits;
// 1 decimal, always meters
function encodeGeoidalSeperation(geoidalSep) {
if (geoidalSep === undefined) {
return ",";
}
return geoidalSep.toFixed(1) + ",M";
}
exports.encodeGeoidalSeperation = encodeGeoidalSeperation;
// Some encodings don't want the unit
function encodeGeoidalSeperationNoUnits(geoidalSep) {
if (geoidalSep === undefined) {
return "";
}
return geoidalSep.toFixed(1);
}
exports.encodeGeoidalSeperationNoUnits = encodeGeoidalSeperationNoUnits;
// degrees
function encodeDegrees(degrees) {
if (degrees === undefined) {
return "";
}
return padLeft(degrees.toFixed(2), 6, "0");
}
exports.encodeDegrees = encodeDegrees;
function encodeDate(date) {
if (date === undefined) {
return "";
}
var year = date.getUTCFullYear();
var month = date.getUTCMonth() + 1;
var day = date.getUTCDate();
return padLeft(day, 2, "0") + padLeft(month, 2, "0") + year.toFixed(0).substr(2);
}
exports.encodeDate = encodeDate;
function encodeTime(date) {
if (date === undefined) {
return "";
}
var hours = date.getUTCHours();
var minutes = date.getUTCMinutes();
var seconds = date.getUTCSeconds();
return padLeft(hours, 2, "0") + padLeft(minutes, 2, "0") + padLeft(seconds, 2, "0");
}
exports.encodeTime = encodeTime;
function encodeValue(value) {
if (value === undefined) {
return "";
}
return value.toString();
}
exports.encodeValue = encodeValue;
// =========================================
// field traditionalDecoders
// =========================================
/**
* Parse the given string to a float, returning 0 for an empty string.
*/
function parseFloatSafe(str) {
if (str === "") {
return 0.0;
}
return parseFloat(str);
}
exports.parseFloatSafe = parseFloatSafe;
/**
* Parse the given string to a integer, returning 0 for an empty string.
*/
function parseIntSafe(i) {
if (i === "") {
return 0;
}
return parseInt(i, 10);
}
exports.parseIntSafe = parseIntSafe;
/**
* Parse the given string to a float if possible, returning 0 for an undefined
* value and a string the the given string cannot be parsed.
*/
function parseNumberOrString(str) {
if (str === undefined) {
return "";
}
var num = parseFloat(str);
return isNaN(num) ? str : num;
}
exports.parseNumberOrString = parseNumberOrString;
/**
* Parses coordinate given as "dddmm.mm", "ddmm.mm", "dmm.mm" or "mm.mm"
*/
function parseDmCoordinate(coordinate) {
var dotIdx = coordinate.indexOf(".");
if (dotIdx < 0) {
return 0;
}
var degrees;
var minutes;
if (dotIdx >= 3) {
degrees = coordinate.substring(0, dotIdx - 2);
minutes = coordinate.substring(dotIdx - 2);
}
else {
// no degrees, just minutes (nonstandard but a buggy unit might do this)
degrees = "0";
minutes = coordinate;
}
return (parseFloat(degrees) + (parseFloat(minutes) / 60.0));
}
exports.parseDmCoordinate = parseDmCoordinate;
/**
* Parses latitude given as "ddmm.mm", "dmm.mm" or "mm.mm" (assuming zero
* degrees) along with a given hemisphere of "N" or "S" into decimal degrees,
* where north is positive and south is negative.
*/
function parseLatitude(lat, hemi) {
var hemisphere = (hemi === "N") ? 1.0 : -1.0;
return parseDmCoordinate(lat) * hemisphere;
}
exports.parseLatitude = parseLatitude;
/**
* Parses latitude given as "dddmm.mm", "ddmm.mm", "dmm.mm" or "mm.mm" (assuming
* zero degrees) along with a given hemisphere of "E" or "W" into decimal
* degrees, where east is positive and west is negative.
*/
function parseLongitude(lon, hemi) {
var hemisphere = (hemi === "E") ? 1.0 : -1.0;
return parseDmCoordinate(lon) * hemisphere;
}
exports.parseLongitude = parseLongitude;
function getYearFromString(yearString, rmcCompatible) {
if (yearString.length === 4) {
return Number(yearString);
}
else if (yearString.length === 2) {
if (rmcCompatible) {
// GPRMC date doesn't specify century. GPS came out in 1973 so if the year
// is less than 73, assume it's 20xx, otherwise assume it is 19xx.
var year = Number(yearString);
if (year < 73) {
year = 2000 + year;
}
else {
year = 1900 + year;
}
return year;
}
else {
return Number("20" + yearString);
}
}
else {
throw Error("Unexpected year string: " + yearString);
}
}
/**
* Parses a UTC time and optionally a date and returns a Date
* object.
* @param {String} time Time the format "hhmmss" or "hhmmss.ss"
* @param {String=} date Optional date in format the ddmmyyyy or ddmmyy
* @returns {Date}
*/
function parseTime(time, date, rmcCompatible) {
if (rmcCompatible === void 0) { rmcCompatible = false; }
if (time === "") {
return new Date(0);
}
var ret = new Date();
if (date) {
var year = date.slice(4);
var month = parseInt(date.slice(2, 4), 10) - 1;
var day = date.slice(0, 2);
ret.setUTCFullYear(getYearFromString(year, rmcCompatible), Number(month), Number(day));
}
ret.setUTCHours(Number(time.slice(0, 2)));
ret.setUTCMinutes(Number(time.slice(2, 4)));
ret.setUTCSeconds(Number(time.slice(4, 6)));
// Extract the milliseconds, since they can be not present, be 3 decimal place, or 2 decimal places, or other?
var msStr = time.slice(7);
var msExp = msStr.length;
var ms = 0;
if (msExp !== 0) {
ms = parseFloat(msStr) * Math.pow(10, 3 - msExp);
}
ret.setUTCMilliseconds(Number(ms));
return ret;
}
exports.parseTime = parseTime;