Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gps

Package Overview
Dependencies
Maintainers
2
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gps - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

.travis.yml

850

gps.js

@@ -1,202 +0,692 @@

var serialport = require("serialport");
var SerialPort = serialport.SerialPort;
var events = require('events');
function GPS() {
events.EventEmitter.call(this);
this.reads = 0
this.collected = []
this.obj = {}
var me = this;
var serialPort = new SerialPort("/dev/cu.SLAB_USBtoUART", {
baudrate: 4800,
parser: serialport.parsers.readline("\n")
});
serialPort.on("open", function () {
serialPort.on('data', function(d){
me.parseGPSData(d)
});
});
/**
* @license GPS.js v0.2.0 26/01/2016
*
* Copyright (c) 2016, Robert Eisele (robert@xarg.org)
* Dual licensed under the MIT or GPL Version 2 licenses.
**/
this.latLngToDecimal = function(coord){
if (coord == undefined) return
negative = (parseInt(coord) < 0)
decimal = null
if (match = coord.match(/^-?([0-9]*?)([0-9]{2,2}\.[0-9]*)$/)){
deg = parseInt(match[1])
min = parseFloat(match[2])
decimal = deg + (min / 60)
if (negative){
decimal *= -1
(function(root) {
var D2R = Math.PI / 180;
var collectSats = [];
function updateState(state, data) {
if (data['type'] === 'RMC' || data['type'] === 'GGA' || data['type'] === 'GLL') {
state['time'] = data['time'];
state['lat'] = data['lat'];
state['lon'] = data['lon'];
}
if (data['type'] === 'ZDA') {
state['time'] = data['time'];
}
if (data['type'] === 'GGA') {
state['alt'] = data['alt'];
}
if (data['type'] === 'RMC'/* || data['type'] === 'VTG'*/) {
// TODO: is rmc speed/track really interchangeable with vtg speed/track?
state['speed'] = data['speed'];
state['track'] = data['track'];
}
if (data['type'] === 'GSA') {
state['satsActive'] = data['satellites'];
state['fix'] = data['fix'];
state['hdop'] = data['hdop'];
state['pdop'] = data['pdop'];
state['vdop'] = data['vdop'];
}
// TODO: better merge algorithm
if (data['type'] === 'GSV') {
var sats = data['satellites'];
for (var i = 0; i < sats.length; i++) {
collectSats.push(sats[i]);
}
// Reset stats
if (data['msgNumber'] === data['msgsTotal']) {
state['satsVisible'] = collectSats;
collectSats = [];
}
}
return decimal
}
this.parseGPSData = function(data){
var that = this;
line = data.split(',')
if (line[0].slice(0,1) != "$") {
return
function parseTime(time, date) {
if (time === '') {
return null;
}
type = line[0].slice(3,6)
var ret = new Date;
if (type == null) {
return
if (date) {
var year = date.slice(4);
var month = date.slice(2, 4) - 1;
var day = date.slice(0, 2);
if (year.length === 4) {
ret.setUTCFullYear(year, month, day);
} else {
// If we need to parse older GPRMC data, we should hack something like
// year < 73 ? 2000+year : 1900+year
ret.setUTCFullYear('20' + year, month, day);
}
}
line.shift()
ret.setUTCHours(time.slice(0, 2));
ret.setUTCMinutes(time.slice(2, 4));
ret.setUTCSeconds(time.slice(4, 6));
ret.setUTCMilliseconds(parseFloat(time.slice(7)) || 0);
switch (type) {
case "GGA":
this.obj.time = line.shift()
this.obj.latitude = this.latLngToDecimal(line.shift())
this.obj.lat_ref = line.shift()
this.obj.longitude = this.latLngToDecimal(line.shift())
this.obj.long_ref = line.shift()
this.obj.quality = line.shift()
this.obj.num_sat = parseInt(line.shift())
this.obj.hdop = line.shift()
this.obj.altitude = line.shift()
this.obj.alt_unit = line.shift()
this.obj.height_geoid = line.shift()
this.obj.height_geoid_unit = line.shift()
this.obj.last_dgps = line.shift()
this.obj.dgps = line.shift()
break;
case "RMC":
this.obj.time = line.shift()
this.obj.validity = line.shift()
this.obj.latitude = this.latLngToDecimal(line.shift())
this.obj.lat_ref = line.shift()
this.obj.longitude = this.latLngToDecimal(line.shift())
this.obj.long_ref = line.shift()
this.obj.speed = line.shift()
this.obj.course = line.shift()
this.obj.date = line.shift()
this.obj.variation = line.shift()
this.obj.var_direction = line.shift()
break;
case "GLL":
this.obj.latitude = this.latLngToDecimal(line.shift())
this.obj.lat_ref = line.shift()
this.obj.longitude = this.latLngToDecimal(line.shift())
this.obj.long_ref = line.shift()
this.obj.time = line.shift()
break;
case "RMA":
line.shift()
this.obj.latitude = this.latLngToDecimal(line.shift())
this.obj.lat_ref = line.shift()
this.obj.longitude = this.latLngToDecimal(line.shift())
this.obj.long_ref = line.shift()
line.shift()
line.shift()
this.obj.speed = line.shift()
this.obj.course = line.shift()
this.obj.variation = line.shift()
this.obj.var_direction = line.shift()
break;
case "GSA":
this.obj.mode = line.shift()
this.obj.mode_dimension = line.shift()
if(this.obj.satellites == undefined) { this.obj.satellites = [] }
for(i=0; i<=11; i++) {
(function(i) {
id = line.shift()
if (id == ''){
that.obj.satellites[i] = {}
} else {
if(that.obj.satellites[i] == undefined) { that.obj.satellites[i] = {} }
that.obj.satellites[i].id = id
}
})(i);
return ret;
}
function parseCoord(coord, dir) {
// Latitude can go from 0 to 90; longitude can go from 0 to 180.
if (coord === '')
return null;
var n, sgn = 1;
switch (dir) {
case 'S':
sgn = -1;
case 'N':
n = 2;
break;
case 'W':
sgn = -1;
case 'E':
n = 3;
break;
}
/*
* Mathematically, but more expensive and not numerical stable:
*
* raw = 4807.038
* deg = Math.floor(raw / 100)
*
* dec = (raw - (100 * deg)) / 60
* res = deg + dec // 48.1173
*/
return sgn * (parseFloat(coord.slice(0, n)) + parseFloat(coord.slice(n)) / 60);
}
function parseNumber(num) {
if (num === '') {
return null;
}
return parseFloat(num);
}
function parseKnots(knots) {
if (knots === '')
return null;
return parseFloat(knots) * 1.852;
}
function parseGSAMode(mode) {
switch (mode) {
case 'M':
return 'manual';
case 'A':
return 'automatic';
case '':
return null;
}
throw 'INVALID GSA MODE: ' + mode;
}
function parseGGAFix(fix) {
switch (fix) {
case '':
case '0':
return null;
case '1':
return 'fix'; // valid SPS fix
case '2':
return 'dgps-fix'; // valid DGPS fix
case '3':
return 'pps-fix'; // valid PPS fix
case '6':
return 'estimated';
case '7':
return 'manual';
case '8':
return 'simulated';
}
throw 'INVALID GGA FIX: ' + fix;
}
function parseGSAFix(fix) {
switch (fix) {
case '1':
case '':
return null;
case '2':
return '2D';
case '3':
return '3D';
}
throw 'INVALID GSA FIX: ' + fix;
}
function parseRMC_GLLStatus(status) {
switch (status) {
case 'A':
return 'active';
case 'V':
return 'void';
case '':
return null;
}
throw 'INVALID RMC/GLL STATUS: ' + status;
}
function parseFAA(faa) {
// Only A and D will correspond to an Active and reliable Sentence
switch (faa) {
case 'A':
return 'autonomous';
case 'D':
return 'differential';
case 'E':
return 'estimated';
case 'M':
return 'manual input';
case 'S':
return 'simulated';
case 'N':
return 'not valid';
case 'P':
return 'precise';
}
throw 'INVALID FAA MODE: ' + faa;
}
function parseRMCVariation(vari, dir) {
if (vari === '' || dir === '')
return null;
var q = (dir === 'W') ? -1.0 : 1.0;
return parseFloat(vari) * q;
}
function isValid(str, crc) {
var checksum = 0;
for (var i = 1; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c === 42) // Asterisk: *
break;
checksum ^= c;
}
return checksum === parseInt(crc, 16);
}
function parseDist(num, unit) {
if (unit === 'M' || unit === '') {
return parseNumber(num);
}
throw 'Unknown unit: ' + unit;
}
function GPS() {
this['events'] = {};
this['state'] = {};
}
GPS.prototype['events'] = null;
GPS.prototype['state'] = null;
GPS['mod'] = {
// Global Positioning System Fix Data
'GGA': function(str, gga) {
if (gga.length !== 16) {
throw 'Invalid GGA length: ' + str;
}
this.obj.pdop = line.shift()
this.obj.hdop = line.shift()
this.obj.vdop = line.shift()
break;
case "GSV":
this.obj.msg_count = line.shift()
this.obj.msg_num = line.shift()
this.obj.num_sat = parseInt(line.shift())
if(this.obj.satellites == undefined) { this.obj.satellites = [] }
/*
11
1 2 3 4 5 6 7 8 9 10 | 12 13 14 15
| | | | | | | | | | | | | | |
$--GGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
for(i=0; i<=3; i++) {
(function(i) {
if(that.obj.satellites[i] == undefined) { that.obj.satellites[i] = {} }
1) Time (UTC)
2) Latitude
3) N or S (North or South)
4) Longitude
5) E or W (East or West)
6) GPS Quality Indicator,
0 = Invalid, 1 = Valid SPS, 2 = Valid DGPS, 3 = Valid PPS
7) Number of satellites in view, 00 - 12
8) Horizontal Dilution of precision, lower is better
9) Antenna Altitude above/below mean-sea-level (geoid)
10) Units of antenna altitude, meters
11) Geoidal separation, the difference between the WGS-84 earth
ellipsoid and mean-sea-level (geoid), '-' means mean-sea-level below ellipsoid
12) Units of geoidal separation, meters
13) Age of differential GPS data, time in seconds since last SC104
type 1 or 9 update, null field when DGPS is not used
14) Differential reference station ID, 0000-1023
15) Checksum
*/
that.obj.satellites[i].elevation = line.shift()
that.obj.satellites[i].azimuth = line.shift()
that.obj.satellites[i].snr = line.shift()
})(i);
return {
'time': parseTime(gga[1]),
'lat': parseCoord(gga[2], gga[3]),
'lon': parseCoord(gga[4], gga[5]),
'alt': parseDist(gga[9], gga[10]),
'quality': parseGGAFix(gga[6]),
'satelites': parseNumber(gga[7]),
'hdop': parseNumber(gga[8]), // dilution
'geoidal': parseDist(gga[11], gga[12]), // aboveGeoid
'age': parseNumber(gga[13]), // dgpsUpdate???
'stationID': parseNumber(gga[14]) // dgpsReference??
};
},
// GPS DOP and active satellites
'GSA': function(str, gsa) {
if (gsa.length !== 19) {
throw 'Invalid GSA length: ' + str;
}
break;
case "HDT":
this.obj.heading = line.shift()
break;
case "ZDA":
this.obj.time = line.shift()
day = line.shift()
month = line.shift()
year = line.shift()
if (year.length > 2){
year = [2, 2]
/*
eg1. $GPGSA,A,3,,,,,,16,18,,22,24,,,3.6,2.1,2.2*3C
eg2. $GPGSA,A,3,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*35
1 = Mode:
M=Manual, forced to operate in 2D or 3D
A=Automatic, 3D/2D
2 = Mode:
1=Fix not available
2=2D
3=3D
3-14 = PRNs of Satellite Vehicles (SVs) used in position fix (null for unused fields)
15 = PDOP
16 = HDOP
17 = VDOP
18 = Checksum
*/
var sats = [];
for (var i = 3; i < 12 + 3; i++) {
if (gsa[i] !== '') {
sats.push(parseInt(gsa[i], 10));
}
}
this.obj.date = day + month + year
this.obj.local_hour_offset = line.shift()
this.obj.local_minute_offset = line.shift()
break;
default:
return {
'mode': parseGSAMode(gsa[1]),
'fix': parseGSAFix(gsa[2]),
'satellites': sats,
'pdop': parseNumber(gsa[15]),
'hdop': parseNumber(gsa[16]),
'vdop': parseNumber(gsa[17])
};
},
// Recommended Minimum data for gps
'RMC': function(str, rmc) {
if (rmc.length !== 13 && rmc.length !== 14) {
throw 'Invalid RMC length: ' + str;
}
/*
$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh
RMC = Recommended Minimum Specific GPS/TRANSIT Data
1 = UTC of position fix
2 = Data status (A-ok, V-invalid)
3 = Latitude of fix
4 = N or S
5 = Longitude of fix
6 = E or W
7 = Speed over ground in knots
8 = Track made good in degrees True
9 = UT date
10 = Magnetic variation degrees (Easterly var. subtracts from true course)
11 = E or W
(12) = NMEA 2.3 introduced FAA mode indicator (A=Autonomous, D=Differential, E=Estimated, N=Data not valid)
12 = Checksum
*/
return {
'time': parseTime(rmc[1], rmc[9]),
'status': parseRMC_GLLStatus(rmc[2]),
'lat': parseCoord(rmc[3], rmc[4]),
'lon': parseCoord(rmc[5], rmc[6]),
'speed': parseKnots(rmc[7]),
'track': parseNumber(rmc[8]),
'variation': parseRMCVariation(rmc[10], rmc[11]),
'faa': rmc.length === 14 ? parseFAA(rmc[12]) : null
};
},
// Track info
'VTG': function(str, vtg) {
if (vtg.length !== 10 && vtg.length !== 11) {
throw 'Invalid VTG length: ' + str;
}
/*
------------------------------------------------------------------------------
1 2 3 4 5 6 7 8 9 10
| | | | | | | | | |
$--VTG,x.x,T,x.x,M,x.x,N,x.x,K,m,*hh<CR><LF>
------------------------------------------------------------------------------
1 = Track degrees
2 = Fixed text 'T' indicates that track made good is relative to true north
3 = not used
4 = not used
5 = Speed over ground in knots
6 = Fixed text 'N' indicates that speed over ground in in knots
7 = Speed over ground in kilometers/hour
8 = Fixed text 'K' indicates that speed over ground is in kilometers/hour
(9) = FAA mode indicator (NMEA 2.3 and later)
9/10 = Checksum
*/
if (vtg[2] === '' && vtg[8] === '' && vtg[6] === '') {
return {
'track': null,
'speed': null,
'faa': null
};
}
if (vtg[2] !== 'T') {
throw 'Invalid VTG track mode: ' + str;
}
if (vtg[8] !== 'K' || vtg[6] !== 'N') {
throw 'Invalid VTG speed tag: ' + str;
}
return {
'track': parseNumber(vtg[1]),
'speed': parseKnots(vtg[5]),
'faa': vtg.length === 11 ? parseFAA(vtg[9]) : null
};
},
// satelites in view
'GSV': function(str, gsv) {
if (gsv.length < 9 || gsv.length % 4 !== 1) {
throw 'Invalid GSV length: ' + str;
}
/*
$GPGSV,1,1,13,02,02,213,,03,-3,000,,11,00,121,,14,13,172,05*67
1 = Total number of messages of this type in this cycle
2 = Message number
3 = Total number of SVs in view
4 = SV PRN number
5 = Elevation in degrees, 90 maximum
6 = Azimuth, degrees from true north, 000 to 359
7 = SNR (signal to noise ratio), 00-99 dB (null when not tracking, higher is better)
8-11 = Information about second SV, same as field 4-7
12-15= Information about third SV, same as field 4-7
16-19= Information about fourth SV, same as field 4-7
8/12/16/20 = Checksum
*/
var sats = [];
for (var i = 4; i < gsv.length - 1; i += 4) {
var prn = parseNumber(gsv[i]);
var snr = parseNumber(gsv[i + 3]);
sats.push({
'prn': prn,
'elevation': parseNumber(gsv[i + 1]),
'azimuth': parseNumber(gsv[i + 2]),
'snr': snr,
'status': prn !== null ? (snr !== null ? 'tracking' : 'in view') : null
});
}
return {
'msgNumber': parseNumber(gsv[2]),
'msgsTotal': parseNumber(gsv[1]),
//'satsInView' : parseNumber(gsv[3]), // Can be obtained by satellites.length
'satellites': sats
};
},
// Geographic Position - Latitude/Longitude
'GLL': function(str, gll) {
if (gll.length !== 9) {
throw 'Invalid GLL length: ' + str;
}
/*
------------------------------------------------------------------------------
1 2 3 4 5 6 7 8
| | | | | | | |
$--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,a,m,*hh<CR><LF>
------------------------------------------------------------------------------
1. Latitude
2. N or S (North or South)
3. Longitude
4. E or W (East or West)
5. Universal Time Coordinated (UTC)
6. Status A - Data Valid, V - Data Invalid
7. FAA mode indicator (NMEA 2.3 and later)
8. Checksum
*/
return {
'time': parseTime(gll[5]),
'status': parseRMC_GLLStatus(gll[6]),
'lat': parseCoord(gll[1], gll[2]),
'lon': parseCoord(gll[3], gll[4])
};
},
// UTC Date / Time and Local Time Zone Offset
'ZDA': function(str, zda) {
/*
1 = hhmmss.ss = UTC
2 = xx = Day, 01 to 31
3 = xx = Month, 01 to 12
4 = xxxx = Year
5 = xx = Local zone description, 00 to +/- 13 hours
6 = xx = Local zone minutes description (same sign as hours)
*/
// TODO: incorporate local zone information
return {
'time': parseTime(zda[1], zda[2] + zda[3] + zda[4])
//'delta': time === null ? null : (Date.now() - time) / 1000
};
}
Object.keys(this.obj).map(function (key) {
val = that.obj[key]
if(val === ""){
delete that.obj[key]
}
});
this.reads ++
this.collected.push(type)
this.collected = this.collected.filter(function(v,i,s){
return that.onlyUnique(v,i,s)
});
if (this.reads > 5 && this.collected.indexOf('GGA') > -1 && this.collected.indexOf('RMC') > -1){
this.emit('location', this.obj)
this.reads = 0
this.collected = []
this.obj = {}
};
GPS['Parse'] = function(line) {
if (typeof line !== 'string')
return false;
var nmea = line.split(',');
var last = nmea.pop();
if (nmea.length < 4 || line.charAt(0) !== '$' || last.indexOf('*') === -1) {
return false;
}
last = last.split('*');
nmea.push(last[0]);
nmea.push(last[1]);
// Remove $ character and first two chars from the beginning
nmea[0] = nmea[0].slice(3);
if (GPS['mod'][nmea[0]] !== undefined) {
// set raw data here as well?
var data = this['mod'][nmea[0]](line, nmea);
data['raw'] = line;
data['valid'] = isValid(line, nmea[nmea.length - 1]);
data['type'] = nmea[0];
return data;
}
return false;
};
// Heading (N=0, E=90, S=189, W=270) from point 1 to point 2
GPS['Heading'] = function(lat1, lon1, lat2, lon2) {
var dlon = (lon2 - lon1) * D2R;
lat1 = lat1 * D2R;
lat2 = lat2 * D2R;
var sdlon = Math.sin(dlon);
var cdlon = Math.cos(dlon);
var slat1 = Math.sin(lat1);
var clat1 = Math.cos(lat1);
var slat2 = Math.sin(lat2);
var clat2 = Math.cos(lat2);
var n = sdlon * clat2;
var d = clat1 * slat2 - slat1 * clat2 * cdlon;
var head = Math.atan2(n, d) * 180 / Math.PI;
return (head + 360) % 360;
};
GPS['Distance'] = function(lat1, lon1, lat2, lon2) {
// Haversine Formula
// R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159
// Because Earth is no exact sphere, rounding errors may be up to 0.5%.
// var RADIUS = 6371; // Earth radius average
// var RADIUS = 6378.137; // Earth radius at equator
var RADIUS = 6372.8; // Earth radius in km
var hLat = (lat2 - lat1) * D2R * 0.5; // Half of lat difference
var hLon = (lon2 - lon1) * D2R * 0.5; // Half of lon difference
lat1 = lat1 * D2R;
lat2 = lat2 * D2R;
var shLat = Math.sin(hLat);
var shLon = Math.sin(hLon);
var clat1 = Math.cos(lat1);
var clat2 = Math.cos(lat2);
var tmp = shLat * shLat + clat1 * clat2 * shLon * shLon;
//return RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
return RADIUS * 2 * Math.asin(Math.sqrt(tmp));
};
GPS.prototype['update'] = function(line) {
var parsed = GPS['Parse'](line);
if (parsed === false)
return false;
updateState(this.state, parsed);
if (this['events']['data'] !== undefined) {
this['events']['data'].call(this, parsed);
}
if (this['events'][parsed.type] !== undefined) {
this['events'][parsed.type].call(this, parsed);
}
return true;
};
GPS.prototype['partial'] = "";
GPS.prototype['updatePartial'] = function(chunk) {
this['partial'] += chunk;
while (true) {
var pos = this['partial'].indexOf("\r\n");
if (pos === -1)
break;
var line = this['partial'].slice(0, pos);
if (line.charAt(0) === '$') {
this['update'](line);
}
this['partial'] = this['partial'].slice(pos + 2);
}
};
GPS.prototype['on'] = function(ev, cb) {
if (this['events'][ev] === undefined) {
this['events'][ev] = cb;
return this;
}
return null;
};
GPS.prototype['off'] = function(ev) {
if (this['events'][ev] !== undefined) {
this['events'][ev] = undefined;
}
return this;
};
if (typeof exports === 'object') {
module.exports = GPS;
} else {
root['GPS'] = GPS;
}
this.onlyUnique = function(value, index, self) {
return self.indexOf(value) === index;
}
}
GPS.prototype.__proto__ = events.EventEmitter.prototype;
module.exports = GPS
})(this);
{
"author": "Andrew Nesbitt <andrewnez@gmail.com> (http://andrew.github.com)",
"homepage": "https://github.com/andrew/node-gps",
"keywords": [
"gps",
"nodecopter",
"nmea"
],
"bugs": {
"url": "https://github.com/andrew/node-gps/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/andrew/node-gps/blob/master/LICENSE"
"name": "gps",
"title": "gps.js",
"version": "0.2.0",
"homepage": "https://github.com/infusion/GPS.js",
"bugs": "https://github.com/infusion/GPS.js/issues",
"description": "A GPS NMEA parser library",
"keywords": ["nmea", "gps", "serial", "parser", "distance", "geo", "location", "rmc", "gga", "gll", "gsa", "vtg", "gva"],
"author": "Robert Eisele <robert@xarg.org> (http://www.xarg.org/)",
"main": "gps",
"private": false,
"readmeFilename": "README.md",
"directories": {
"example": "examples"
},
"license": "MIT OR GPL-2.0",
"repository": {
"type": "git",
"url": "git://github.com/infusion/GPS.js.git"
},
"engines": {
"node": "*"
},
"scripts": {
"test": "mocha tests/*.js"
},
"devDependencies": {
"express": "*",
"socket.io": "*",
"serialport": "*",
"angles": "*",
"mocha": "*",
"chai": "*",
"byline": "*"
}
],
"main": "./sass.js",
"repository": {
"type": "git",
"url": "git://github.com/andrew/node-gps.git"
},
"name": "gps",
"version": "0.1.0",
"description": "GPS event emitter",
"main": "gps.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"serialport": "~1.2.2"
}
}

@@ -1,37 +0,325 @@

# node-gps
Read GPS data from GPS serial devices
![GPS.js](https://github.com/infusion/GPS.js/blob/master/res/logo.png?raw=true "Javascript GPS Parser")
## Install
[![NPM Package](https://img.shields.io/npm/v/gps.svg?style=flat)](https://npmjs.org/package/gps "View this project on npm")
[![Build Status](https://travis-ci.org/infusion/GPS.js.svg?branch=master)](https://travis-ci.org/infusion/GPS.js)
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
```bash
npm install gps
GPS.js is an extensible parser for [NMEA](http://www.gpsinformation.org/dale/nmea.htm) sentences, given by any common GPS receiver. The output is tried to be as high-level as possible to make it more useful than simply splitting the information. The aim is, that you don't have to understand NMEA, just plug in your receiver and you're ready to go.
Usage
===
The interface of GPS.js is as simple as the following few lines. You need to add an event-listener for the completion of the task and invoke the update method with a sentence you want to process. There are much more examples in the examples folder.
```javascript
var gps = new GPS;
// Add an event listener on all protocols
gps.on('data', function(parsed) {
console.log(parsed);
});
// Call the update routine directly with a NMEA sentence, which would
// come from the serial port or stream-reader normally
gps.update("$GPGGA,224900.000,4832.3762,N,00903.5393,E,1,04,7.8,498.6,M,48.0,M,,0000*5E");
```
## Usage
It's also possible to add event-listeners only on one of the following protocols, by stating `gps.on('GGA', ...)` for example.
State
===
The real advantage over other NMEA implementations is, that the GPS information is interpreted and normalized. The most high-level API is the state object, which changes with every new event. You can use this information with:
```javascript
var GPS = require('gps')
gps.on('data', function() {
console.log(gps.state);
});
```
var gps = new GPS();
gps.on('location', function(data) {
console.log(data);
Installation
===
Installing GPS.js is as easy as cloning this repo or use the following command:
```
npm install --save gps
```
Find the serial device
===
On Linux serial devices typically have names like `/dev/ttyS1`, on OSX `/dev/tty.usbmodem1411` after installing a USB to serial driver and on Windows, you're probably fine by using the highest COM device you can find in the device manager. Please note that if you have multople USB ports on your computer and use them randomly, you have to lookup the path/device again.
If you find yourself on a BeagleBone, the serial device must be registered manually. Luckily, this can be done within node quite easily using [octalbonescript](https://www.npmjs.com/package/octalbonescript):
```javascript
var obs = require('octalbonescript');
obs.serial.enable('/dev/ttyS1', function() {
console.log('serial device activated');
});
```
## Development
Examples
===
Source hosted at [GitHub](http://github.com/andrew/node-gps).
Report Issues/Feature requests on [GitHub Issues](http://github.com/andrew/node-gps).
GPS.js comes with some examples, like drawing the current latutude and longitude to Google Maps, displaying a persistent state and displaying the parsed raw data. In some cases you have to adjust the serial path to your own GPS receiver to make it work.
### Note on Patches/Pull Requests
Simple serial example
---
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a future version unintentionally.
* Send me a pull request. Bonus points for topic branches.
```javascript
var SerialPort = require('serialport');
var port = new SerialPort.SerialPort('/dev/tty.usbserial', { // change path
baudrate: 4800,
parser: SerialPort.parsers.readline('\r\n')
});
## Copyright
var GPS = require('gps');
var gps = new GPS;
Copyright (c) 2013 Andrew Nesbitt. See [LICENSE](https://github.com/andrew/node-gps/blob/master/LICENSE) for details.
gps.on('data', function(data) {
console.log(data, gps.state);
});
port.on('data', function(data) {
gps.update(data);
});
```
Dashboard
---
Go into the folder `examples/dashboard` and start the server with
```
node server
```
After that you can open the browser and go to http://localhost:3000 The result should look like, which in principle is just a visualiziation of the state object `gps.state`
![GPS TU Dresden](https://github.com/infusion/GPS.js/blob/master/res/dashboard.png?raw=true)
Google Maps
---
Go into the folder `examples/maps` and start the server with
```
node server
```
After that you can open the browser and go to http://localhost:3000 The result should look like
![GPS Google Maps Dresden](https://github.com/infusion/GPS.js/blob/master/res/maps.png?raw=true)
Confluence
---
[Confluence](http://www.confluence.org/) is a project, which tries to travel to and document all integer GPS coordinates. GPS.js can assist on that goal. Go into the examples folder and run:
```
node confluence
```
You should see something like the following, updating as you move around
```
You are at (48.53, 9.05951),
The closest confluence point (49, 9) is in 51.36 km.
You have to go 355.2° N
```
Set Time
---
On systems without a RTC - like Raspberry PI - you need to update the time yourself at runtime. If the device has an internet connection, it's quite easy to use an NTP server. An alternative for disconnected projects with access to a GPS receiver can be the high-precision time signal, sent by satellites. Go to the examples folder and run the following to update the time:
```
node set-time
```
Available Methods
===
update(line)
---
The update method is the most important function, it adds a new NMEA sentence and forces the callback to trigger
updatePartial(chunk)
---
Will call `update()` when a full NMEA sentence has been arrived
on(event, callback)
---
Adds an event listener for a protocol to occur (see implemented protocols, simply use the name - upper case) or for all sentences with `data`. Because GPS.js should be more general, it doesn't inherit `EventEmitter`, but simply invokes the callback.
off(event)
---
Removes an event listener
Implemented Protocols
===
GGA - Fix information
---
Gets the data, you're most probably looking for: *latitude and longitude*
The parsed object will have the following attributes:
- type: "GGA"
- time: The time given as a JavaScript Date object
- lat: The latitude
- lon: The longitude
- alt: The altitude
- quality: Fix quality (either invalid, fix or diff)
- satelites: Number of satellites being tracked
- hdop: Horizontal [dilution of precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS))
- geoidal: Height of geoid (mean sea level)
- age: time in seconds since last DGPS update
- stationID: DGPS station ID number
- valid: Indicates if the checksum is okay
RMC - NMEAs own version of essential GPS data
---
Similar to GGA but gives also delivers the velocity
The parsed object will have the following attributes:
- type: "RMC"
- time: The time given as a JavaScript Date object
- status: Status active or void
- lat: The latitude
- lon: The longitude
- speed: Speed over the ground in km/h
- track: Track angle in degrees
- variation: Magnetic Variation
- faa: The FAA mode, introduced with NMEA 2.3
- valid: Indicates if the checksum is okay
GSA - Active satellites
---
The parsed object will have the following attributes:
- type: "GSA"
- mode: Auto selection of 2D or 3D fix (either auto or manual)
- fix: The selected fix mode (either 2D or 3D)
- satellites: Array of satellite IDs
- pdop: Position [dilution of precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS))
- vdop: Vertical [dilution of precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS))
- hdop: Horizontal [dilution of precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS))
- valid: Indicates if the checksum is okay
GLL - Geographic Position - Latitude/Longitude
---
The parsed object will have the following attributes:
- type: "GLL"
- lat: The latitude
- lon: The longitude
- status: Status active or void
- time: The time given as a JavaScript Date object
- valid: Indicates if the checksum is okay
GSV - List of Satellites in view
---
GSV messages are paginated. `msgNumber` indicates the current page and `msgsTotal` is the total number of pages.
The parsed object will have the following attributes:
- type: "GSV"
- msgNumber: Current page
- msgsTotal: Number of pages
- satellites: Array of satellite objects with the following attributes:
- prn: Satellite PRN number
- elevation: Elevation in degrees
- azimuth: Azimuth in degrees
- snr: Signal to Noise Ratio (higher is better)
- valid: Indicates if the checksum is okay
VTG - vector track and speed over ground
---
The parsed object will have the following attributes:
- type: "VTG"
- track: Track in degrees
- speed: Speed over ground in km/h
- faa: The FAA mode, introduced with NMEA 2.3
- valid: Indicates if the checksum is okay
ZDA - UTC day, month, and year, and local time zone offset
---
The parsed object will have the following attributes:
- type: "ZDA"
- time: The time given as a JavaScript Date object
GPS State
===
If the streaming API is not needed, but a solid state of the system, the `gps.state` object can be used. It has the following properties:
- time: Current time
- lat: Latitude
- lon: Longitude
- alt: Altitude
- satsActive: Array of active satellites
- speed: Speed over ground in km/h
- track: Track in degrees
- satsVisible: Array of all visible satellites
Adding new protocols is a matter of minutes. If you need a protocol which isn't implemented, I'm happy to see a pull request or a new ticket.
Troubleshooting
===
If you don't get valid position information after turning on the receiver, chances are high you simply have to wait as it takes some [time to first fix](https://en.wikipedia.org/wiki/Time_to_first_fix).
Functions
===
GPS.js comes with a few static functions, which help by working with geo-coordinates.
GPS.Parse(line)
---
Parses a single line and returns the resulting object, in case the callback system isn't needed/wanted
GPS.Distance(latFrom, lonFrom, latTo, lonTo)
---
Calculates the distance between two geo-coordinates using Haversine formula
GPS.Heading(latFrom, lonFrom, latTo, lonTo)
---
Calculates the angle from one coordinate to another. Heading is represented as windrose coordinates (N=0, E=90, S=189, W=270). The result can be used as the argument of [angles](https://github.com/infusion/Angles.js) `compass()` method:
```javascript
var angles = require('angles');
console.log(angles.compass(GPS.Heading(50, 10, 51, 9))); // will return x ∈ { N, S, E, W, NE, ... }
```
Using GPS.js with the browser
===
The use cases should be rare to parse NMEA directly inside the browser, but it works too.
```html
<script src="gps.js"></script>
<script>
var gps = new GPS;
gps.update('...');
</script>
```
Testing
===
If you plan to enhance the library, make sure you add test cases and all the previous tests are passing. You can test the library with
```
npm test
```
Copyright and licensing
===
Copyright (c) 2016, Robert Eisele (robert@xarg.org)
Dual licensed under the MIT or GPL Version 2 licenses.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc