GPS.js is an extensible NMEA 0183 parser and geospatial utility library for JavaScript. It turns the text sentences emitted by common GNSS receivers into typed JavaScript objects, maintains a normalized state across successive messages, and works in Node.js as well as browsers. Version 0.8.1 provides ESM, CommonJS, browser builds, and TypeScript declarations through the npm package gps.
What GPS.js Does
A GNSS receiver performs the radio-frequency measurements and computes its navigation solution internally. Most modules then expose that solution over a serial connection as NMEA sentences such as GGA, RMC, and GSV. GPS.js begins at that boundary: it does not calculate a position from raw satellite signals, but parses the receiver's text output into coordinates, time, altitude, velocity, fix quality, dilution of precision, and satellite information.
The parser recognizes talker identifiers for GPS, GLONASS, Galileo, BeiDou, and QZSS where the sentence carries that distinction. Coordinates are converted from NMEA degrees-and-minutes notation into signed decimal degrees, speed is normalized to kilometers per hour, timestamps become JavaScript Date objects, and every parsed message retains its original sentence and checksum result.
Installation
npm install gps
The package exposes equivalent ESM and CommonJS entry points:
import GPS from 'gps'; const GPS = require('gps'); GPS.js 0.8.1 also ships gps.d.mts and gps.d.ts, so TypeScript resolves the matching declarations for either module system without a separate @types package.
Parse a Complete NMEA Sentence
Create one parser for each independent receiver stream. Subscribe to data to receive every recognized sentence, then pass complete lines to update():
import GPS from 'gps';
const gps = new GPS();
gps.on('data', (message) => {
if (!message.valid) {
console.warn('Checksum mismatch:', message.raw);
return;
}
console.log(message.type, message.lat, message.lon);
});
gps.update(
'$GPGGA,224900.000,4832.3762,N,00903.5393,E,1,04,7.8,498.6,M,48.0,M,,0000*5E'
); The callback receives a sentence-specific object. A GGA message includes latitude, longitude, altitude, fix quality, satellite count, horizontal dilution of precision, geoidal separation, and DGPS fields. Every parsed object also has type, raw, and valid properties.
update() returns true when the sentence type and structure were recognized. A checksum mismatch does not make parsing fail: the message is emitted with valid: false, allowing an application to inspect or log damaged input. Always check valid before using receiver data for decisions.
Listen for One Sentence Type
Use the uppercase sentence name instead of data when only one protocol matters. Multiple listeners can subscribe to the same event, and off() removes either a specific callback or all callbacks for an event:
function reportFix(message) {
if (!message.valid || message.quality === null) return;
console.log({
latitude: message.lat,
longitude: message.lon,
altitude: message.alt,
satellites: message.satellites,
hdop: message.hdop,
});
}
gps.on('GGA', reportFix);
gps.off('GGA', reportFix); Feed a Node.js Serial Stream
Serial reads do not necessarily align with NMEA lines: one chunk may contain half a sentence or several complete sentences. updatePartial() buffers arbitrary chunks and invokes update() whenever it finds an LF or CRLF delimiter. With the current serialport API, a minimal receiver connection looks like this:
npm install gps serialport
import GPS from 'gps';
import { SerialPort } from 'serialport';
const gps = new GPS();
const port = new SerialPort({
path: '/dev/ttyUSB0',
baudRate: 9600,
});
gps.on('data', (message) => {
if (message.valid) console.log(message, gps.state);
});
port.on('data', (chunk) => {
try {
gps.updatePartial(chunk.toString('ascii'));
} catch (error) {
console.error('Malformed NMEA sentence:', error.message);
}
});
port.on('error', (error) => {
console.error('Serial port error:', error.message);
}); Replace /dev/ttyUSB0 with the device exposed by the operating system. Common alternatives include /dev/ttyACM0, /dev/ttyS0, a macOS path below /dev/tty.*, or a Windows name such as COM4. The baud rate must match the receiver configuration; 9600 baud is common but not universal.
Supported NMEA Sentences
| Type | Information | Important fields |
|---|---|---|
GGA | Fix data | Position, altitude, quality, satellites, HDOP |
GSA | Fix mode and active satellites | 2D/3D fix, PRNs, PDOP, HDOP, VDOP |
RMC | Recommended minimum navigation data | Time, position, status, speed, track |
VTG | Course and speed over ground | True track, magnetic track, speed |
GSV | Satellites in view | PRN, elevation, azimuth, SNR, constellation |
GLL | Geographic position | Time, latitude, longitude, status |
ZDA | UTC date and time | Timestamp, local offset |
HDT | Heading | True-north heading |
GST | Position error statistics | RMS and error ellipse components |
GRS | Range residuals | Residual mode and values |
GBS | Satellite fault detection | Expected errors and likely failed satellite |
GNS | Multi-GNSS fix data | Position, mode, satellites, HDOP, altitude |
TXT | Receiver text transmission | Multipart text, ID, constellation |
Newer NMEA revisions add optional system, signal, navigation-status, and FAA-mode fields to several sentences. GPS.js accepts the supported legacy and extended layouts and exposes those additions when present. Unknown sentence types return false rather than producing an untyped array.
Work with the Aggregated State
Individual NMEA sentences divide the navigation solution across several messages. The mutable gps.state object combines the latest compatible values so an application does not have to join GGA, RMC, GSA, GSV, VTG, HDT, and ZDA manually:
gps.on('data', (message) => {
if (!message.valid) return;
const {
time,
lat,
lon,
alt,
speed,
track,
fix,
hdop,
satsActive,
satsVisible,
processed,
errors,
} = gps.state;
console.log({
time,
lat,
lon,
alt,
speed,
track,
fix,
hdop,
active: satsActive?.length ?? 0,
visible: satsVisible?.length ?? 0,
processed,
errors,
});
}); Fields are absent or null until a corresponding sentence arrives. processed counts lines passed to update(), while errors counts rejected structures and failed multipart TXT assembly. Visible satellites are collected across constellations and expire from the state when they have not been observed recently.

The dashboard example visualizes the same state object: the polar plot maps satellite azimuth and elevation, the bars compare signal-to-noise ratios, and the information block shows the latest position, fix, dilution values, and satellite counts.
Parse Without Events
GPS.Parse() is useful for files, tests, and request/response code where persistent state and events are unnecessary:
const message = GPS.Parse(
'$GPGGA,224900.000,4832.3762,N,00903.5393,E,1,04,7.8,498.6,M,48.0,M,,0000*5E'
);
if (message && message.valid) {
console.log(message.lat, message.lon);
} The static parser returns a typed object for a recognized sentence and falsetry...catch when input comes from an untrusted or noisy transport.
Distance, Route Length, and Heading
GPS.js includes three helpers for ordinary geographic coordinates:
const distanceKm = GPS.Distance(48.5329, 9.0590, 48.7758, 9.1829);
const headingDeg = GPS.Heading(48.5329, 9.0590, 48.7758, 9.1829);
const routeKm = GPS.TotalDistance([
{ lat: 48.5329, lon: 9.0590 },
{ lat: 48.6500, lon: 9.1000 },
{ lat: 48.7758, lon: 9.1829 },
]); Distance() uses the Haversine formula and returns kilometers. TotalDistance() sums that distance across adjacent points, and Heading() returns the initial bearing in degrees with north at 0, east at 90, south at 180, and west at 270. The result can be converted into a compass label with Angles.js.
The distance model treats Earth as a sphere. It is suitable for dashboards, route summaries, and proximity checks, but it is not a geodetic survey calculation; ellipsoidal methods are preferable when sub-percent accuracy matters over long distances.
Use GPS.js in a Browser
The browser build exposes a global GPS constructor. It is useful when NMEA data reaches the page over Web Serial, WebSocket, or a recorded fixture:
<script src="gps.min.js"></script>
<script>
const gps = new GPS();
gps.on('RMC', (message) => {
if (message.valid) console.log(message.lat, message.lon, message.speed);
});
</script> A browser normally cannot open an arbitrary operating-system serial device without user permission. GPS.js parses NMEA text after the application has obtained it; transport selection, reconnection, and device authorization remain outside the library.
Reliability and Trust Boundaries
- An NMEA checksum detects common transmission damage but provides no authentication. A device or process that can inject serial data can also inject plausible coordinates.
- A valid checksum does not imply a valid navigation fix. Check status, fix quality, and relevant dilution or error fields before accepting a position.
- Do not assume every update contains every state field. Receivers emit sentence families at different rates, and optional values can remain null.
- Keep one GPS.js instance per independent stream. Combining unrelated receivers would merge their latest sentences into one state object.
- Bound and supervise the surrounding stream. A transport that never sends a newline can make any line-oriented accumulator retain an incomplete chunk indefinitely.