1. NMEA定位数据概览
NMEA定位数据是一种广泛应用于GPS与航海设备的文本序列格式,常见于从卫星接收器到计算机的实时数据传输。它以ASCII字符构成,每条句子以“$”开头,字段之间用逗号分隔,通常以“*”后跟CRC结束,最后再加上回车换行。了解这套数据的结构,是实现Java解析NMEA定位数据的第一步。
常见的句型包括GGA、RMC、GSA、GSV、GLL等,每种句型承载不同的定位信息。例如GGA给出定位的时间、经纬度、海拔和信号卫星信息;RMC提供定位状态、速度和航向;GSV显示可见卫星的数量与轨迹信息。
原始串的获取与校验通常来自串口、USB转串口或网络流。为了确保后续解析的可靠性,处理流程必须实现CRC校验、句子分割和去噪处理,避免脏数据进入解析逻辑。本文的实战指南以完整的原始NMEA串到GPS定位的整个流程为目标。
1.1 NMEA句型与字段概览
GGA句子包含定位时刻、纬度、经度、定位质量、使用卫星数、HDOP、海拔和海拔参照等信息;RMC句子提供推荐的定位要素:状态、纬度、经度、地面速度、航向、UTC时间以及日期。对开发者来说,最常用的解析组合往往是
下面给出一个示例GGA句子,直观展示字段位置及含义;随后会在代码中演示如何将这些字段转换为可用的数据模型。
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
2. 常见句型及其含义
GGA经纬度、时间、定位质量和卫星信息是最直接用于构建定位的基础数据源。GGA的字段顺序通常包括:时间、纬度、N/S、经度、E/W、定位质量、卫星数、HDOP、海拔及单位等。
RMC是一个综合性较强的句型,提供状态(A有效、V未有效)、经纬度、地面速度、地面航向以及日期等。RMC常用于计算航路和状态监控。
GSA/GSV句型用于精度评估与可见卫星信息,辅助判断信号品质与定位鲁棒性。GSA给出DOP参数(PDOP、HDOP、VDOP),GSV列出卫星详细信息。
2.1 GGA与RMC字段含义对照
在实际应用中,通常需要将字段映射到我们的数据模型中,例如将经纬度由度分格式转换为十进制度数、将UTC时间转换为本地时区时间等。理解字段含义,是实现正确解析的关键步骤。
下面给出一个简化的解析流程示例,帮助你把句子字段与数据模型对齐:
// GGA部分字段简化对照
// 0: sentence type, 1: UTC时间, 2: lat, 3: N/S, 4: lon, 5: E/W, 6: quality, 7: satellites, 8: hdop, 9: altitude
3. Java解析核心:从原始串到字段
设计要点包括:CRC校验、句子分割、句型识别、字段解析与数据模型映射、坐标转换以及容错处理。实现清晰的解耦,可以让后续扩展变得更加简单,也方便将来支持更多句型。
数据模型与转换逻辑应该包含经纬度的十进制表示、时间戳、速度、航向、卫星数量以及定位质量等字段,以便后续的定位、地图显示或数据统计使用。
3.1 解析流程设计
典型的解析流程如下:读取原始NMEA串 -> 验证CRC -> 过滤无效句子 -> 根据句型分派处理 -> 提取字段并进行单位转换 -> 填充数据模型 -> 返回或持续更新状态。

在实现中,优先实现一个统一的句子处理入口,例如 parseSentence(String sentence) 方法,内部根据 sentenceId(如GGA、RMC)分派到对应的解析器。这样可以保持代码的整洁和扩展性。下面给出一个简化的数据模型与CRC校验方法示例。
public class NMEAData {public double latitude; // 十进制纬度public double longitude; // 十进制经度public String timeUTC; // UTC时间,格式如 HHMMSSpublic int satellites; // 可用卫星数public int fixQuality; // 定位质量public double altitude; // 海拔,单位为米public double speed; // 地面速度,单位为节public double course; // 地面航向,度
}
private static boolean checkCRC(String sentence) {int a = 0;int i = 1;while (i < sentence.length() && sentence.charAt(i) != '*') {a ^= sentence.charAt(i++);}if (i >= sentence.length()) return false;int crc = Integer.parseInt(sentence.substring(i + 1), 16);return a == crc;
}
3.2 坐标转换:NMEA经纬度转十进制度数
NMEA的纬度、经度以度分格式表示,例如纬度“4807.038,N”表示48度07.038分。转换为十进制需要将度取整为度部分,分钟部分除以60再加到度上,并根据指北(N/S、E/W)决定正负号。
private static double toDecimal(String degMin, String dir) {if (degMin == null || degMin.isEmpty()) return 0.0;double d = Double.parseDouble(degMin);int deg = (int)(d / 100);double minutes = d - deg * 100;double dec = deg + minutes / 60.0;if ("S".equalsIgnoreCase(dir) || "W".equalsIgnoreCase(dir)) dec = -dec;return dec;
}
4. 实战示例:解析GGA/RMC并计算GPS定位
在真实场景中,你会从串口或网络流中读取持续的NMEA串,循环解析并实时更新定位结果。核心目标是实现一个稳定的解析器,能够处理丢包、噪声和异常句子,同时尽可能快速地将坐标更新到应用层。
实时解析流程通常包括:打开数据源 -> 按行读取 -> 遇到句子时执行CRC校验 -> 将句子分派给GGA/RMC解析器 -> 更新当前定位状态 -> 将结果输出或绘制到地图。
下面给出一个简化的整合示例,展示如何在Java中整合GGA与RMC的解析逻辑,并输出当前定位坐标:
public class NMEAParser {private NMEAData current = new NMEAData();public NMEAData parseSentence(String sentence) {if (!checkCRC(sentence)) return null;String body = sentence.substring(1, sentence.indexOf('*'));String[] parts = body.split(",");String type = parts[0].substring(2); // 跳过话务者前缀如GPif ("GGA".equals(type)) {current.latitude = toDecimal(parts[2], parts[3]);current.longitude = toDecimal(parts[4], parts[5]);current.timeUTC = parts[1];current.satellites = Integer.parseInt(parts[7]);current.fixQuality = Integer.parseInt(parts[6]);current.altitude = Double.parseDouble(parts[9]);} else if ("RMC".equals(type)) {current.latitude = toDecimal(parts[3], parts[4]);current.longitude = toDecimal(parts[5], parts[6]);current.timeUTC = parts[1];current.speed = Double.parseDouble(parts[7]);current.course = Double.parseDouble(parts[8]);}return current;}// checkCRC, toDecimal 等方法在这里定义...
}
// 使用示例
public static void main(String[] args) {NMEAParser parser = new NMEAParser();String gga = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47";String rmc = "$GPRMC,123520,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A";NMEAData d1 = parser.parseSentence(gga);System.out.println("Lat: " + d1.latitude + " Lon: " + d1.longitude);NMEAData d2 = parser.parseSentence(rmc);System.out.println("Speed(kts): " + d2.speed);
}
5. 数据可靠性与时间戳处理
CRC校验与句子完整性是确保数据可靠性的基石。若CRC校验失败,应忽略该句子并继续读取下一句,以防止脏数据污染定位结果。
时间戳与时区转换中的UTC时间通常以HHMMSS格式给出,日期在RMC句子中以DDMMYY给出,需结合两者计算出本地时间或ISO时间。由于NMEA时间一般为UTC,转化时需要考虑时区与夏令时等因素。
坐标精度与单位在GGA/RMC中经纬度单位多为度分格式,务必要在输出前进行十进制度数转换,否则地理信息的呈现会出现明显偏差。海拔单位往往为米,若要与其他系统对接,需在模型层面统一单位。
5.1 CRC校验与句子完整性
CRC计算通常从“$”之后到星号前的内容逐字节按位异或,结果与星号后的十六进制数进行比较。示例实现如下,确保仅在通过CRC后才继续解析:
private static boolean checkCRC(String sentence) {int a = 0;int i = 1;while (i < sentence.length() && sentence.charAt(i) != '*') {a ^= sentence.charAt(i++);}if (i >= sentence.length()) return false;int crc = Integer.parseInt(sentence.substring(i + 1), 16);return a == crc;
}
5.2 时间戳处理与本地化
将UTC时间与本地时区对齐,通常需要结合日期字段进行解析。若在RMC中获得日期,如230394,需将其转化为LocalDateTime,并对夏时制进行校正。时间处理对轨迹绘制和事件时间序列分析尤为关键。
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;private static LocalDateTime toDateTime(String timeUTC, String dateStr) {// timeUTC: HHMMSS, dateStr: DDMMYYint hour = Integer.parseInt(timeUTC.substring(0, 2));int min = Integer.parseInt(timeUTC.substring(2, 4));int sec = Integer.parseInt(timeUTC.substring(4, 6));int dd = Integer.parseInt(dateStr.substring(0, 2));int mm = Integer.parseInt(dateStr.substring(2, 4));int yy = Integer.parseInt(dateStr.substring(4, 6)) + 2000;LocalDate date = LocalDate.of(yy, mm, dd);return LocalDateTime.of(date, java.time.LocalTime.of(hour, min, sec));
}
5.3 坐标精度与对外接口
在输出到地图、日志或数据库时,建议保留一定的小数位数以确保定位精度,并统一使用十进制度数表示,避免因单位换算导致的定位偏差。若结合多源数据,应对数据中的缺失字段设置默认值或空值策略,以提高系统鲁棒性。


