Python library for parsing and generating NMEA 0183 protocol messages used in GPS and marine navigation systems
77
GPS positioning sentences provide location data, satellite information, and positioning quality metrics from GPS and GNSS receivers. These sentences form the core of most NMEA data streams from GPS devices.
Essential GPS fix information including position, altitude, and quality indicators.
class GGA(TalkerSentence, ValidGGAFix, LatLonFix):
"""Global Positioning System Fix Data."""
timestamp: datetime.time
lat: str # Latitude in DDMM.MMMM format
lat_dir: str # 'N' or 'S'
lon: str # Longitude in DDDMM.MMMM format
lon_dir: str # 'E' or 'W'
gps_qual: str # GPS quality indicator (0-8)
num_sats: str # Number of satellites
horizontal_dil: str # Horizontal dilution of precision
altitude: float # Altitude above mean sea level
altitude_units: str # Units of altitude (usually 'M')
geo_sep: str # Geoidal separation
geo_sep_units: str # Units of geoidal separation
age_gps_data: str # Age of differential GPS data
ref_station_id: str # Reference station ID
@property
def latitude(self) -> float:
"""Latitude in signed decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in signed decimal degrees."""
@property
def is_valid(self) -> bool:
"""True if GPS quality indicates valid fix (1-5)."""import pynmea2
# Parse GGA sentence
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")
print(f"Time: {msg.timestamp}") # 18:43:53.070000
print(f"Position: {msg.latitude}, {msg.longitude}") # -19.4840833333, 24.1751
print(f"Raw coordinates: {msg.lat}{msg.lat_dir}, {msg.lon}{msg.lon_dir}") # 1929.045S, 02410.506E
print(f"GPS Quality: {msg.gps_qual}") # 1 (GPS fix)
print(f"Satellites: {msg.num_sats}") # 04
print(f"HDOP: {msg.horizontal_dil}") # 2.6
print(f"Altitude: {msg.altitude} {msg.altitude_units}") # 100.0 M
print(f"Valid fix: {msg.is_valid}") # True
# Create GGA sentence
gga_data = ['184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000']
new_msg = pynmea2.GGA('GP', 'GGA', gga_data)
print(str(new_msg)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6DMinimum recommended navigation information including position, speed, course, and date.
class RMC(TalkerSentence, ValidRMCStatusFix, LatLonFix, DatetimeFix):
"""Recommended Minimum Specific GPS/TRANSIT Data."""
timestamp: datetime.time
status: str # 'A' = Active, 'V' = Void
lat: str # Latitude in DDMM.MMMM format
lat_dir: str # 'N' or 'S'
lon: str # Longitude in DDDMM.MMMM format
lon_dir: str # 'E' or 'W'
spd_over_grnd: str # Speed over ground in knots
true_course: str # Track angle in degrees
datestamp: datetime.date # Date
mag_variation: str # Magnetic variation
mag_var_dir: str # Direction of magnetic variation
mode_indicator: str # Mode indicator (A, D, E, etc.)
nav_status: str # Navigational status
@property
def latitude(self) -> float:
"""Latitude in signed decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in signed decimal degrees."""
@property
def datetime(self) -> datetime.datetime:
"""Combined date and time."""
@property
def is_valid(self) -> bool:
"""True if status and mode indicators are valid."""import pynmea2
# Parse RMC sentence
msg = pynmea2.parse("$GPRMC,184353.07,A,1929.045,S,02410.506,E,0.13,309.62,120598,A*70")
print(f"Time: {msg.timestamp}") # 18:43:53.070000
print(f"Date: {msg.datestamp}") # 1998-05-12
print(f"Combined: {msg.datetime}") # 1998-05-12 18:43:53.070000
print(f"Status: {msg.status}") # A (Active)
print(f"Position: {msg.latitude}, {msg.longitude}") # -19.4840833333, 24.1751
print(f"Speed: {msg.spd_over_grnd} knots") # 0.13 knots
print(f"Course: {msg.true_course}°") # 309.62°
print(f"Valid: {msg.is_valid}") # TrueGeographic position information with status and optional FAA mode indicator.
class GLL(TalkerSentence, ValidStatusFix, LatLonFix):
"""Geographic Position - Latitude/Longitude."""
lat: str # Latitude in DDMM.MMMM format
lat_dir: str # 'N' or 'S'
lon: str # Longitude in DDDMM.MMMM format
lon_dir: str # 'E' or 'W'
timestamp: datetime.time # UTC time
status: str # 'A' = valid, 'V' = invalid
faa_mode: str # FAA mode indicator
@property
def latitude(self) -> float:
"""Latitude in signed decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in signed decimal degrees."""
@property
def is_valid(self) -> bool:
"""True if status is 'A'."""import pynmea2
# Parse GLL sentence
msg = pynmea2.parse("$GPGLL,1929.045,S,02410.506,E,184353.07,A,A*4C")
print(f"Position: {msg.latitude}, {msg.longitude}") # -19.4840833333, 24.1751
print(f"Time: {msg.timestamp}") # 18:43:53.070000
print(f"Status: {msg.status}") # A
print(f"FAA Mode: {msg.faa_mode}") # A
print(f"Valid: {msg.is_valid}") # TrueCombined GPS/GLONASS fix data with mode indicators for different satellite systems.
class GNS(TalkerSentence, LatLonFix):
"""GNSS Fix Data."""
timestamp: datetime.time
lat: str # Latitude in DDMM.MMMM format
lat_dir: str # 'N' or 'S'
lon: str # Longitude in DDDMM.MMMM format
lon_dir: str # 'E' or 'W'
mode_indicator: str # Mode indicator for each system
num_sats: str # Number of satellites
hdop: str # Horizontal dilution of precision
altitude: str # Antenna altitude above geoid
geo_sep: str # Geoidal separation
age_gps_data: str # Age of differential data
diferential: str # Differential reference station ID
@property
def latitude(self) -> float:
"""Latitude in signed decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in signed decimal degrees."""Satellite dilution of precision and active satellite information.
class GSA(TalkerSentence, ValidGSAFix):
"""GNSS DOP and Active Satellites."""
mode: str # Selection mode (M = Manual, A = Auto)
mode_fix_type: str # Fix type (1 = None, 2 = 2D, 3 = 3D)
sv_id01: str # Satellite ID channel 1
sv_id02: str # Satellite ID channel 2
sv_id03: str # Satellite ID channel 3
sv_id04: str # Satellite ID channel 4
sv_id05: str # Satellite ID channel 5
sv_id06: str # Satellite ID channel 6
sv_id07: str # Satellite ID channel 7
sv_id08: str # Satellite ID channel 8
sv_id09: str # Satellite ID channel 9
sv_id10: str # Satellite ID channel 10
sv_id11: str # Satellite ID channel 11
sv_id12: str # Satellite ID channel 12
pdop: str # Position dilution of precision
hdop: str # Horizontal dilution of precision
vdop: str # Vertical dilution of precision
@property
def is_valid(self) -> bool:
"""True if fix type is 2D or 3D."""import pynmea2
# Parse GSA sentence
msg = pynmea2.parse("$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39")
print(f"Mode: {msg.mode}") # A (Auto)
print(f"Fix type: {msg.mode_fix_type}") # 3 (3D fix)
print(f"Active satellites: {[s for s in [msg.sv_id01, msg.sv_id02, msg.sv_id04, msg.sv_id05, msg.sv_id09, msg.sv_id12, msg.sv_id24] if s]}")
print(f"PDOP: {msg.pdop}") # 2.5
print(f"HDOP: {msg.hdop}") # 1.3
print(f"VDOP: {msg.vdop}") # 2.1
print(f"Valid: {msg.is_valid}") # True (3D fix)Information about satellites in view including elevation, azimuth, and signal strength.
class GSV(TalkerSentence):
"""GNSS Satellites in View."""
num_messages: str # Total number of GSV messages
msg_num: str # Current message number
num_sv_in_view: str # Total satellites in view
# Satellite 1 information
sv_prn_num_1: str # Satellite PRN number
elevation_deg_1: str # Elevation in degrees
azimuth_1: str # Azimuth in degrees
snr_1: str # Signal-to-noise ratio
# Satellite 2 information
sv_prn_num_2: str
elevation_deg_2: str
azimuth_2: str
snr_2: str
# Satellite 3 information
sv_prn_num_3: str
elevation_deg_3: str
azimuth_3: str
snr_3: str
# Satellite 4 information
sv_prn_num_4: str
elevation_deg_4: str
azimuth_4: str
snr_4: strimport pynmea2
# Parse GSV sentence
msg = pynmea2.parse("$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75")
print(f"Message {msg.msg_num} of {msg.num_messages}") # Message 1 of 2
print(f"Satellites in view: {msg.num_sv_in_view}") # 8
# Satellite 1 info
print(f"Sat {msg.sv_prn_num_1}: El={msg.elevation_deg_1}° Az={msg.azimuth_1}° SNR={msg.snr_1}")
# Sat 01: El=40° Az=083° SNR=46
# Process all satellites in this message
for i in range(1, 5): # GSV contains up to 4 satellites per message
prn = getattr(msg, f'sv_prn_num_{i}')
if prn:
elevation = getattr(msg, f'elevation_deg_{i}')
azimuth = getattr(msg, f'azimuth_{i}')
snr = getattr(msg, f'snr_{i}')
print(f"Satellite {prn}: Elevation={elevation}°, Azimuth={azimuth}°, SNR={snr}")class GST(TalkerSentence):
"""GNSS Pseudorange Error Statistics."""
timestamp: datetime.time
rms: str # RMS value of standard deviation
std_dev_major: str # Standard deviation of semi-major axis
std_dev_minor: str # Standard deviation of semi-minor axis
orientation: str # Orientation of semi-major axis
std_dev_latitude: str # Standard deviation of latitude error
std_dev_longitude: str # Standard deviation of longitude error
std_dev_altitude: str # Standard deviation of altitude errorclass GRS(TalkerSentence):
"""GNSS Range Residuals."""
timestamp: datetime.time
residuals_mode: str # Residuals mode (0 = used, 1 = calculated)
sv_res_01: str # Satellite 1 residual
sv_res_02: str # Satellite 2 residual
# ... sv_res_03 through sv_res_12
sv_res_12: str # Satellite 12 residualAll GPS positioning sentences that include LatLonFix mixin provide convenient coordinate conversion:
import pynmea2
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")
# Decimal degrees (most common)
print(f"Lat: {msg.latitude}") # -19.4840833333
print(f"Lon: {msg.longitude}") # 24.1751
# Minutes and seconds components
print(f"Lat minutes: {msg.latitude_minutes}") # 29.045
print(f"Lon minutes: {msg.longitude_minutes}") # 4.506
print(f"Lat seconds: {msg.latitude_seconds}") # 2.7
print(f"Lon seconds: {msg.longitude_seconds}") # 0.36
# Format coordinates
print(f'{msg.latitude:.6f}°, {msg.longitude:.6f}°') # -19.484083°, 24.175100°
print(f'{abs(msg.latitude):.4f}°{msg.lat_dir}, {abs(msg.longitude):.4f}°{msg.lon_dir}') # 19.4841°S, 24.1751°EInstall with Tessl CLI
npx tessl i tessl/pypi-pynmea2docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10