feat: 导入 NMEA-0183 GPS 协议解析库(node-nmea)
NMEA-0183 语句解析器,含 21 个语句类型 codec 与配套单元测试。 - codecs/:APB/BWC/DBT/GGA/GLL/GRMT/GSA/GSV/HDG/HDM/HDT/MWV/RDID/ RMB/RMC/ROT/RSA/TXT/VTG/VWR/ZDA - test/:各 codec 对应的 mocha 用例 - .github/workflows/:node与 npm-publish 流水线 许可证:MIT(原作者 James Penn)
This commit is contained in:
356
helpers.js
Normal file
356
helpers.js
Normal file
@@ -0,0 +1,356 @@
|
||||
//Copied from from https://github.com/nherment/node-nmea/blob/master/lib/Helper.js
|
||||
|
||||
var m_hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
|
||||
exports.toHexString = function(v) {
|
||||
var lsn;
|
||||
var msn;
|
||||
|
||||
msn = (v >> 4) & 0x0f;
|
||||
lsn = (v >> 0) & 0x0f;
|
||||
return m_hex[msn] + m_hex[lsn];
|
||||
};
|
||||
|
||||
exports.padLeft = function(s, len, ch) {
|
||||
while(s.length < len) {
|
||||
s = ch + s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
// verify the checksum
|
||||
exports.verifyChecksum = function(sentence, checksum) {
|
||||
var q;
|
||||
var c1;
|
||||
var c2;
|
||||
var i;
|
||||
|
||||
// skip the $
|
||||
i = 1;
|
||||
|
||||
// init to first character
|
||||
c1 = sentence.charCodeAt(i);
|
||||
|
||||
// process rest of characters, zero delimited
|
||||
for( i = 2; i < sentence.length; ++i) {
|
||||
c1 = c1 ^ sentence.charCodeAt(i);
|
||||
}
|
||||
|
||||
// checksum is a 2 digit hex value
|
||||
c2 = parseInt(checksum, 16);
|
||||
|
||||
// should be equal
|
||||
return ((c1 & 0xff) === c2);
|
||||
};
|
||||
|
||||
// generate a checksum for a sentence (no trailing *xx)
|
||||
exports.computeChecksum = function(sentence) {
|
||||
var c1;
|
||||
var i;
|
||||
|
||||
// skip the $
|
||||
i = 1;
|
||||
|
||||
// init to first character var count;
|
||||
|
||||
c1 = sentence.charCodeAt(i);
|
||||
|
||||
// process rest of characters, zero delimited
|
||||
for( i = 2; i < sentence.length; ++i) {
|
||||
c1 = c1 ^ sentence.charCodeAt(i);
|
||||
}
|
||||
|
||||
return '*' + exports.toHexString(c1);
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// field encoders
|
||||
// =========================================
|
||||
|
||||
// encode latitude
|
||||
// input: latitude in decimal degrees
|
||||
// output: latitude in nmea format
|
||||
// ddmm.mmm
|
||||
exports.encodeLatitude = function(lat) {
|
||||
var d;
|
||||
var m;
|
||||
var f;
|
||||
var h;
|
||||
var s;
|
||||
var t;
|
||||
if(lat === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if(lat < 0) {
|
||||
h = 'S';
|
||||
lat = -lat;
|
||||
} else {
|
||||
h = 'N';
|
||||
}
|
||||
// get integer degrees
|
||||
d = Math.floor(lat);
|
||||
// degrees are always 2 digits
|
||||
s = d.toString();
|
||||
if(s.length < 2) {
|
||||
s = '0' + s;
|
||||
}
|
||||
// get fractional degrees
|
||||
f = lat - d;
|
||||
// convert to fractional minutes
|
||||
m = (f * 60.0);
|
||||
// format the fixed point fractional minutes
|
||||
t = m.toFixed(3);
|
||||
if(m < 10) {
|
||||
// add leading 0
|
||||
t = '0' + t;
|
||||
}
|
||||
|
||||
s = s + t + ',' + h;
|
||||
return s;
|
||||
};
|
||||
|
||||
// encode longitude
|
||||
// input: longitude in decimal degrees
|
||||
// output: longitude in nmea format
|
||||
// dddmm.mmm
|
||||
exports.encodeLongitude = function(lon) {
|
||||
var d;
|
||||
var m;
|
||||
var f;
|
||||
var h;
|
||||
var s;
|
||||
var t;
|
||||
|
||||
if(lon === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if(lon < 0) {
|
||||
h = 'W';
|
||||
lon = -lon;
|
||||
} else {
|
||||
h = 'E';
|
||||
}
|
||||
|
||||
// get integer degrees
|
||||
d = Math.floor(lon);
|
||||
// degrees are always 3 digits
|
||||
s = d.toString();
|
||||
while(s.length < 3) {
|
||||
s = '0' + s;
|
||||
}
|
||||
|
||||
// get fractional degrees
|
||||
f = lon - d;
|
||||
// convert to fractional minutes and round up to the specified precision
|
||||
m = (f * 60.0);
|
||||
// minutes are always 6 characters = mm.mmm
|
||||
t = m.toFixed(3);
|
||||
if(m < 10) {
|
||||
// add leading 0
|
||||
t = '0' + t;
|
||||
}
|
||||
s = s + t + ',' + h;
|
||||
return s;
|
||||
};
|
||||
|
||||
// 1 decimal, always meters
|
||||
exports.encodeAltitude = function(alt) {
|
||||
if(alt === undefined) {
|
||||
return ',';
|
||||
}
|
||||
return alt.toFixed(1) + ',M';
|
||||
};
|
||||
|
||||
// 1 decimal, always meters
|
||||
exports.encodeGeoidalSeperation = function(geoidalSep) {
|
||||
if(geoidalSep === undefined) {
|
||||
return ',';
|
||||
}
|
||||
return geoidalSep.toFixed(1) + ',M';
|
||||
};
|
||||
|
||||
// magnetic variation
|
||||
exports.encodeMagVar = function(v) {
|
||||
var a;
|
||||
var s;
|
||||
if(v === undefined) {
|
||||
return ',';
|
||||
}
|
||||
a = Math.abs(v);
|
||||
s = (v < 0) ? (a.toFixed(1) + ',E') : (a.toFixed(1) + ',W');
|
||||
return exports.padLeft(s, 7, '0');
|
||||
};
|
||||
|
||||
// degrees
|
||||
exports.encodeDegrees = function(d) {
|
||||
if(d === undefined) {
|
||||
return '';
|
||||
}
|
||||
return exports.padLeft(d.toFixed(2), 6, '0');
|
||||
};
|
||||
|
||||
exports.encodeDate = function(d) {
|
||||
var yr;
|
||||
var mn;
|
||||
var dy;
|
||||
|
||||
if(d === undefined) {
|
||||
return '';
|
||||
}
|
||||
yr = d.getUTCFullYear();
|
||||
mn = d.getUTCMonth() + 1;
|
||||
dy = d.getUTCDate();
|
||||
return exports.padLeft(dy.toString(), 2, '0') + exports.padLeft(mn.toString(), 2, '0') + yr.toString().substr(2);
|
||||
};
|
||||
|
||||
exports.encodeTime = function(d) {
|
||||
var h;
|
||||
var m;
|
||||
var s;
|
||||
|
||||
if(d === undefined) {
|
||||
return '';
|
||||
}
|
||||
h = d.getUTCHours();
|
||||
m = d.getUTCMinutes();
|
||||
s = d.getUTCSeconds();
|
||||
return exports.padLeft(h.toString(), 2, '0') + exports.padLeft(m.toString(), 2, '0') + exports.padLeft(s.toString(), 2, '0');
|
||||
};
|
||||
|
||||
exports.encodeKnots = function(k) {
|
||||
if(k === undefined) {
|
||||
return '';
|
||||
}
|
||||
return exports.padLeft(k.toFixed(1), 5, '0');
|
||||
};
|
||||
|
||||
exports.encodeValue = function(v) {
|
||||
if(v === undefined) {
|
||||
return '';
|
||||
}
|
||||
return v.toString();
|
||||
};
|
||||
|
||||
exports.encodeFixed = function(v, f) {
|
||||
if(v === undefined) {
|
||||
return '';
|
||||
}
|
||||
return v.toFixed(f);
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// field traditionalDecoders
|
||||
// =========================================
|
||||
|
||||
// separate number and units
|
||||
exports.parseAltitude = function(alt, units) {
|
||||
var scale = 1.0;
|
||||
if(units === 'F') {
|
||||
scale = 0.3048;
|
||||
}
|
||||
return parseFloat(alt) * scale;
|
||||
};
|
||||
|
||||
// separate degrees value and quadrant (E/W)
|
||||
exports.parseDegrees = function(deg, quadrant) {
|
||||
var q = (quadrant === 'E') ? -1.0 : 1.0;
|
||||
|
||||
return parseFloat(deg) * q;
|
||||
};
|
||||
|
||||
// fields can be empty so have to wrap the global parseFloat
|
||||
exports.parseFloatX = function(f) {
|
||||
if(f === '') {
|
||||
return 0.0;
|
||||
}
|
||||
return parseFloat(f);
|
||||
};
|
||||
|
||||
// decode latitude
|
||||
// input : latitude in nmea format
|
||||
// first two digits are degress
|
||||
// rest of digits are decimal minutes
|
||||
// output : latitude in decimal degrees
|
||||
exports.parseLatitude = function(lat, hemi) {
|
||||
var h = (hemi === 'N') ? 1.0 : -1.0;
|
||||
var a;
|
||||
var dg;
|
||||
var mn;
|
||||
var l;
|
||||
a = lat.split('.');
|
||||
if(a[0].length === 4) {
|
||||
// two digits of degrees
|
||||
dg = lat.substring(0, 2);
|
||||
mn = lat.substring(2);
|
||||
} else if(a[0].length === 3) {
|
||||
// 1 digit of degrees (in case no leading zero)
|
||||
dg = lat.substring(0, 1);
|
||||
mn = lat.substring(1);
|
||||
} else {
|
||||
// no degrees, just minutes (nonstandard but a buggy unit might do this)
|
||||
dg = '0';
|
||||
mn = lat;
|
||||
}
|
||||
// latitude is usually precise to 5-8 digits
|
||||
return ((parseFloat(dg) + (parseFloat(mn) / 60.0)) * h).toFixed(8);
|
||||
};
|
||||
|
||||
// decode longitude
|
||||
// first three digits are degress
|
||||
// rest of digits are decimal minutes
|
||||
exports.parseLongitude = function(lon, hemi) {
|
||||
var h;
|
||||
var a;
|
||||
var dg;
|
||||
var mn;
|
||||
h = (hemi === 'E') ? 1.0 : -1.0;
|
||||
a = lon.split('.');
|
||||
if(a[0].length === 5) {
|
||||
// three digits of degrees
|
||||
dg = lon.substring(0, 3);
|
||||
mn = lon.substring(3);
|
||||
} else if(a[0].length === 4) {
|
||||
// 2 digits of degrees (in case no leading zero)
|
||||
dg = lon.substring(0, 2);
|
||||
mn = lon.substring(2);
|
||||
} else if(a[0].length === 3) {
|
||||
// 1 digit of degrees (in case no leading zero)
|
||||
dg = lon.substring(0, 1);
|
||||
mn = lon.substring(1);
|
||||
} else {
|
||||
// no degrees, just minutes (nonstandard but a buggy unit might do this)
|
||||
dg = '0';
|
||||
mn = lon;
|
||||
}
|
||||
// longitude is usually precise to 5-8 digits
|
||||
return ((parseFloat(dg) + (parseFloat(mn) / 60.0)) * h).toFixed(8);
|
||||
};
|
||||
|
||||
// fields can be empty so have to wrap the global parseInt
|
||||
exports.parseIntX = function(i) {
|
||||
if(i === '') {
|
||||
return 0;
|
||||
}
|
||||
return parseInt(i, 10);
|
||||
};
|
||||
|
||||
exports.parseDateTime = function(date, time) {
|
||||
var h = parseInt(time.slice(0, 2), 10);
|
||||
var m = parseInt(time.slice(2, 4), 10);
|
||||
var s = parseInt(time.slice(4, 6), 10);
|
||||
var D = parseInt(date.slice(0, 2), 10);
|
||||
var M = parseInt(date.slice(2, 4), 10) - 1; // UTC = 0..11
|
||||
var Y = parseInt(date.slice(4, 6), 10);
|
||||
// hack : GPRMC date doesn't specify century. GPS came out in 1973
|
||||
// so if year is less than 73 its 2000, otherwise 1900
|
||||
if (Y < 73) {
|
||||
Y = Y + 2000;
|
||||
}
|
||||
else {
|
||||
Y = Y + 1900;
|
||||
}
|
||||
|
||||
return new Date(Date.UTC(Y, M, D, h, m, s));
|
||||
};
|
||||
Reference in New Issue
Block a user